Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Today
Total
관리 메뉴

고리타분한 개발자

함수 (Function) - 스코프와 네임스페이스 (Scopes and Namespaces) 본문

JavaScript/Garden

함수 (Function) - 스코프와 네임스페이스 (Scopes and Namespaces)

sunlee334 2017. 11. 11. 16:41

Javascript는 '{}' 블럭이 배배 꼬여 있어도 문법적으로는 잘 처리하지만, Block Scope는 지원하지 않는다. 그래서, Javascript에서는 항상 함수 스코프를 사용한다.


function monkey() { // Scope
for (var i = 0; i < 10; i++) { // Scope가 아님
// count
}
console.log(i); // 10
}


그리고, Javascript에는 Namespace 개념이 없기 때문에 모든 값이 하나의 전역 스코프에 정의된다.


변수를 참조할 때마다 Javascript는 해당 변수를 찾을 때까지 상위 방향으로 스코프를 탐색한다. 변수 탐색을 하다가 전역 스코프에서도 찾지 못하면 ReferenceError를 발생시킨다. 


전역 변수 문제


// script A
monkey = '42';

// script B
var monkey = '42'


이 두 스크립트는 전혀 다릅니다. Script A는 전역 스코프에서 monkey라는 변수를 정의하는 것이고 Scipt B는 현재 스코프에 변수 monkey를 정의하는 것이다. 


다시 말하면, 이 둘은 전혀 다르고 var가 없을때 특별한 의미가 있다.


// Global Scope
var monkey = 42;

function test() {
// local Scope
monkey = 21;
}
test();
monkey; // 21


test 함수안에 있는 'monkey' 변수에 var 구문을 빼면 Global Scope의 foo의 값을 바꿔버린다. Javascript에서 코드가 길어지면 var를 빼먹어서 생긴 버그를 해결하기는 어렵다.


// Global Scope
var items = [ /* some list */ ];
for (var i = 0; i < 10; i++) {
subLoop();
}

function subLoop() {
// Scope of subLoop
for (i = 0; i < 10; i++) { // var가 없다.
// 내가 for문도 해봐서 아는데...
}
}


subLoop 함수는 전역 변수 i의 값을 변경해버리기 때문에 외부에 있는 for문은 subLoop를 한번 호출하고나면 종료됩니다. 두번째 for문에 var를 사용하여 i를 정의하면 이 문제는 생기지 않습니다. 즉, 의도적으로 외부 스코프의 변수를 사용하는 것이 아니라면 var를 꼭 넣어주어야 합니다


지역변수


Javascript에서 지역 변수는 함수의 파라미터와 var로 정의한 변수입니다.


// 전역 공간 var monkey = 1; var animal = 2; var i = 2;

function test(i) {
// test 함수의 지역 공간
i = 5;

var monkey = 3;
animal = 4;
}
test(10);


foo변수와 i변수는 test함수 스코프에 있는 지역 변수라서 전역 공간에 있는 monkey, i 값은 바뀌지 않는다. 하지만, animal은 전역변수이기 때문에 전역 공간에 있는 animal의 값이 변경됩니다.


호이스팅(Hoisting)


Javascript는 선언문을 모두 호이스팅 한다. 호이스팅이란 var구문이나 function 선언문을 해당 스코프의 맨 위로 옮기는 것을 말합니다,


animal();
var bar = function() {};
var someValue = 42;

test();animal

function test(data) {
if (false) {
monkey = 1;

} else {
var monkey = 2;
}
for (var i = 0; i < 100; i++) {
var e = data[i];
}
}


코드를 본격적으로 실행하기 이전에 Javascript는 var 구문과 function 선언문을 해당 스코프의 맨 위로 옮깁니다.


// var 구문이 여기로 옮겨짐.
var animal, someValue; // default to 'undefined'

// function 선언문도 여기로 옮겨짐
function test(data) {
var monkey, i, e; // Block Scope은 없으므로 local 변수들은 여기로 옮겨짐
if (false) {
monkey = 1;

} else {
monkey = 2;
}
for (i = 0; i < 100; i++) {
e = data[i];
}
}

animal(); // animal()가 아직 'undefined'이기 때문에 TypeError가 남
someValue = 42; // Hoisting은 할당문은 옮기지 않는다.
animal = function() {};

test();


블록 스코프(Block Scope)는 없으므로 for문과 if문 안에 있는 var 구문들까지도 모두 함수 스코프 앞쪽으로 옮겨집니다. 그래서, if Block의 결과는 좀 이상해집니다. 


원래 코드에서 if Block은 전역 변수 monkey를 바꾸는 것처럼 보였지만 호이스팅 후에는 지역변수를 바꿉니다.


호이스팅을 모르면 다음과 같은 코드는 referenceError를 낼 것으로 예상 되어질 것입니다.


// SomeImportantThing이 초기화됐는지 검사합니다.
if (!SomeImportantThing) {
var SomeImportantThing = {};
}


var 구문은 전역 스코프의 맨위로 옮겨지기 때문에 이 코드는 잘 동작합니다. 


var SomeImportantThing;

// SomeImportantThing을 여기서 초기화하거나 말거나...

// SomeImportantThing는 선언돼 있다.
if (!SomeImportantThing) {
SomeImportantThing = {};
}


이름 찾는 순서


Javascript의 모든 Scope는 현 객체를 가리키는 this를 가지고 있습니다. 전역 스코프에도 this가 있습니다.


함수 스코프에는 arguments라는 변수가 하나 더 있습니다. 이 변수는 함수에 인자로 넘겨진 값들이 담겨 있습니다.


예를 들어, 함수 스코프에서 monkey라는 변수에 접근할 때 Javacript는 다음과 같은 순서로 찾습니다.


1. 해당 Scope에서 var monkey 구문으로 선언된 것을 찾는다.

2. Function 파라미터에서 monkey라는 것을 찾는다.

3. 해당 Function 이름이 monkey인치 찾습니다.

4. 상위 Scope로 있는지 확인하고 있으면 1부터 다시 시작한다.


네임스페이스


Javascript에서는 전역 공간(Namespace)이 하나밖에 없어서 변수 이름이 중복되기 쉽습니다. 하지만, 이름없는 랩퍼(Anonymous Wrapper)를 사용하여 쉽게 처리할 수 있습니다.


(function() {
// 일종의 네임스페이스라고 할 수 있다.

window.monkey = function() {
// 이 클로저는 전역 스코프에 노출된다.
};

})(); // 함수를 정의하자마자 실행한다.


이름없는 함수는 표현식(expression)이기 때문에 호출되려면 먼저 평가(Evaluate) 되어야 합니다.


( // 소괄호 안에 있는 것을 먼저 평가한다.
function() {}
) // 그리고 함수 객체를 반환한다.
() // 평가된 결과를 호출한다.


함수를 평가하고 바로 호출하는 방법이 몇가지 더 있습니다.


// 함수를 평가하자마자 호출하는 방법들...
! function() {}(); +
function() {}();
(function() {}());


결론


코드를 캡슐화할 때는 항상 이름없는 랩퍼(Anonymous Wapper)를 사용하여 네임스페이스를 만들어 사용할 것을 추천합니다. 이 래퍼(Wapper)는 이름이 중복되는 것을 막아주고 쉽게 모듈화할 수 있도록 도와 줍니다.


전역변수를 사용하는것은 좋지 못한 습관입니다.


License

이 게시글은 JavaScript Garden을 참고하여 작성 되었습니다. (https://github.com/BonsaiDen/JavaScript-Garden)






Comments