Lexical Scope in Javascript

Node.js 디자인 패턴 책을 공부하면서 Lexical Scope에 대한 언급이 있어 정리하고자 한다.

Lexical Scope는 스코프가 코드가 작성된 때에 적용이 되는 것이다.
Static Scope (정적 스코프) 라고도 한다. (개인적으로 lexical이라는 단어 때문에 더 헷갈리는게 아닐까싶다)
Dynamic Scope는 코드가 작성될 때가 아닌 코드를 실행하면서(런타임 시에) 스코프가 정해지는 것을 의미한다.

개인적으로 단어로만 이해하기 애매해서 코드를 통해 Lexical Scope를 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
var str = 'original';

function outer() {
var str = 'new';
inner();
}

function inner() {
console.log(str);
}

outer(); // original

outer 함수를 호출하면 outer 함수에서 할당한 new가 아니라 original이 출력된다.
(물론 outer 함수에서 var를 없애면 str 변수에 값이 새로 할당되서 new가 출력된다.)

original이 출력되는 이유는 inner 함수를 작성할 때의 str 변수는 original로 선언한 str 변수를 바라보고 있기 때문이다.
그렇기 때문에 런타임시에 inner 함수는 outer 함수 내에서 실행되지만,
Lexical Scope로 인해 inner 함수의 스코프가 outer 함수에 종속되지 않기 때문에 original이 출력된 것이다.

(Perl과 같은 언어에서는 Dynamic Scope를 사용할 수 있다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var str = 'original';

function outer() {
var str = 'new';
(function() {
console.log(str);
})();
}

function inner() {
console.log(str);
}

outer(); // new

이 코드는 outer 함수 내부에 있는 함수는 코드 작성 시 outer 함수 내부의 var str = ‘new’;를 바라보고 있기 때문에 new를 출력한다.

표현하자면 이런 식으로 나타낼 수 있을 것이다.


그럼 이것을 왜 알아야 하면 Arrow function의 동작에 대해 어느정도 도움이 되기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name;
}

Person.prototype.say = function() {
setTimeout(function() {
console.log(`Hello I'm ${this.name}`);
}, 0);
};

const devson = new Person('devson');
devson.say(); // Hello I'm undefined

앞서 설명을 어느정도 이해했다면 왜 setTimeout 내부의 this.nameundefined가 인지 감이 올 수 있을 것이다.
setTimeoutPerson.say 함수와는 별개의 Scope를 가지고 있기 때문에 this가 Person을 바라보고 있는 것이 아니라,
브라우저라면 window, Node.js라면 global을 바라보고 있다.

thisPerson으로 정하고자 할 때는 bind를 써서 아래와 같이 binding 할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name;
}

Person.prototype.say = function() {
setTimeout((function() {
console.log(`Hello I'm ${this.name}`);
}).bind(this), 200);
};

const devson = new Person('devson');
devson.say(); // Hello I'm devson

여기서 setTimeout에서 callback 함수를 Arrow function으로 사용하면 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name;
}

Person.prototype.say = function() {
setTimeout(() => {
console.log(`Hello I'm ${this.name}`);
}, 100);
};

const devson = new Person('devson');
devson.say(); // Hello I'm devson

function() {…} 을 사용했을 때는 undefined가 나오더니 지금은 Person.name이 출력됐다.
그 이유는 Arrow function의 경우 Person의 Lexical Scope가 binding 되기 때문이다.


Javascript는 this가 Java 복잡하기 때문에 Lexical Scope에 대한 이해가 필요한 것 같다.

참고:
TOAST Scope & Closure: https://meetup.toast.com/posts/86
MDN Arrow Function (kor): https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/%EC%95%A0%EB%A1%9C%EC%9A%B0_%ED%8E%91%EC%85%98#%EB%B0%94%EC%9D%B8%EB%94%A9_%EB%90%98%EC%A7%80_%EC%95%8A%EC%9D%80_this

Share