자바스크립트 고수가 되어보자
호이스팅
js 엔진은 실행 컨텍스트
에 실행이 가능한 코드를 구분하기 위한 과정을 거친다. 이러한 과정 중, let, const, function 등의 선언
을 스코프
에 등록한다.
동작을 제외한 선언이 코드의 실행보다 먼저 메모리에 적재된다고 이해하자.
함수를 호출해서 사용하는 경우, undefined
이라는 값을 자주 보게된다. 변수가 어떻게 생성되는 지를 알아야 undefined
라는 값의 정체를 알 수 있다.
- 선언 단계 (Declaration phase)
- 초기화 단계 (Initialization phase)
- 할당 단계 (Assignment phase)
선언 단계에서 변수를 실행 컨텍스트에 등록한다. 해당 변수는 스코프가 참조하는 대상이 된다.
다음으로 변수 객체에 등록된 변수를 위해서 메모리를 할당한다. 이 과정에서 변수는 undefined
으로 초기화된다.
마지막으로 undefined
로 초기화된 변수에 실제 값을 할당한다.
(* var 키워드로 선언한 변수는 선언과 초기화가 한번에 이루어진다.)
let 키워드로 선언한 변수는 선언과 초기화 과정이 분리되어 이루어진다. 즉 선언은 했지만, 아직 초기화가 이루어지지 않은 단계에서 변수에 접근하면 메모리가 아직 할당되기도 전 이므로 참조 에러가 발생한다. 초기화 이전까지는 메모리가 할당되기 이전 시점이며 당연하게도 변수를 참조할 수 없고, 이를 TDZ(Temporal Dead Zone)
라고 한다.
스코프 체인
수백, 수천 개의 변수들이 유효한 범위, 정확히 원하는 시점에 메모리에 적재되고 사라지는 걸 지정하는 것이 중요하다.
scope chain 이라는 단어 자체에 의미를 전부 내포하고 있다. scope 는 변수 참조값의 경계를 담고 있는 박스이다. 이 박스를 전역 -> 지역으로 계층이 존재하는 형태로 참조된다.
- brower 에선
window
- Node 에서는
global
또한 중요한 점은 상위 박스를 참조할 수는 있지만, 하위 박스를 참조할 수 없다는 것이다. 이건 Java 의 상속과 동일하다.
global scope, local scope 와 block scope 도 존재한다. 아래 그림을 보면 scope 가 어떻게 이루어지는 지 알 수 있다.
호출 스택
this
일반함수 this
- 호출 방식에 의해
this
에 바인딩되는 객체가 동적으로 결정된다.
아래 예시를 살펴보자.
1
2
3
4
5
6
7
function foo() {
console.log(this); // this -> window
function bar() {
console.log(this); // this -> window
}
}
메서드 내부함수인 경우, this
는 전역객체에 바인딩된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const value = 1;
const obj = {
value: 999,
foo: function () {
console.log(this); // obj
console.log(this.value); // 100
function bar() {
console.log(this); // window
console.log(this.value); // 1
}
bar();
},
};
obj.foo();
콜백함수인 경우, this
는 전역객체에 바인딩된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const value = 1;
const obj = {
value: 100,
foo: function () {
setTimeout(function () {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
},
};
obj.foo();
내부함수는 일반 함수, 메서드, 콜백함수 선언 위치 관계없이 this
는 전역객체를 바인딩한다.
- 설계 단계의 결함으로, 메서드가 내부함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미
- 이를 회피하기 위해
that
방식 (1) - 혹은 명시적으로
this
를 바인딩할 수 있도록apply
call
bind
- 메서드를 제공한다. (2)
- 이를 회피하기 위해
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const value = 1;
const obj = {
value: 100,
foo: function () {
const that = this; // Workaround : this === obj
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
console.log("bar's that: ", that); // obj
console.log("bar's that.value: ", that.value); // 100
}
bar();
},
};
obj.foo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const value = 1;
const obj = {
value: 100,
foo: function () {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar(a, b) {
console.log("bar's this: ", this); // obj
console.log("bar's this.value: ", this.value); // 100
console.log("bar's arguments: ", arguments);
}
bar.apply(obj, [1, 2]);
bar.call(obj, 1, 2);
bar.bind(obj)(1, 2);
},
};
obj.foo();
메서드가 실행되는 경우를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj1 = {
name: 'Lee',
speakName: function () {
console.log(this.name);
},
};
const obj2 = {
name: 'Kim',
};
obj2.speakName = obj1.speakName;
obj1.speakName;
obj2.speakName;
함수가 객체의 프로퍼티에 속하는 경우, 메서드로 호출된다.
- 메서드 내부의
this
는 해당 메서드를 소유한 객체 - 해당 메서드를 호출한 객체에 바인딩된다.
생성자 함수
const myObject = new myFunction();
- 빈 객체 생성, this 바인딩
- 이 때 this 는 빈 객체를 가리킨다.
- this 를 통한 프로퍼티 생성
- 생성된 객체 반환
- 반환문이 없는 경우, this 에 바인딩 된 새로 생성된 객체가 반환
- 반환문이 this 가 아닌 다른 객체를 명시적으로 반환하는 경우
- this 가 아닌 명시적으로 지정한 객체가 반환된다.
따라서 일반함수, 생성자 함수의 차이를 정리해보면
- 일반함수 호출
- this : 전역객체 (브라우저는 window)
- 생성자 함수
- 생성한 빈 객체
화살표함수 this
await / async
closure
Prototype
- js 의 모든 객체는 부모 객체와 연결됨
- 부모 객체를
Prototype
라고 함 - 모든 객체는 자신의
프로토타입 객체
를 가리키는- [[Prototype]] 이라는
internal slot
을 갖는다. - 상속을 위해 사용된다.
- 함수 는 일반 객체와는 다르게
prototype
프로퍼티를 소유하게 된다.
- [[Prototype]] 이라는
- 부모 객체를
한방 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name) {
this.name = name;
}
const foo = new Person('Lee');
// 프로토타입 객체의 변경
Person.prototype = { gender: 'male' };
const bar = new Person('Kim');
console.log(foo.gender); // undefined
console.log(bar.gender); // 'male'
console.log(foo.constructor); // ① Person(name)
console.log(bar.constructor); // ② Object()
Q. 그래서 Prototype
이 뭔지 왜 알아야 하는가?
non-static
메서드를 만들고자 사용된다.