Post

자바스크립트 고수가 되어보자

호이스팅

출처는 이곳

js 엔진은 실행 컨텍스트에 실행이 가능한 코드를 구분하기 위한 과정을 거친다. 이러한 과정 중, let, const, function 등의 선언스코프에 등록한다.

동작을 제외한 선언이 코드의 실행보다 먼저 메모리에 적재된다고 이해하자.

함수를 호출해서 사용하는 경우, undefined 이라는 값을 자주 보게된다. 변수가 어떻게 생성되는 지를 알아야 undefined 라는 값의 정체를 알 수 있다.

  1. 선언 단계 (Declaration phase)
  2. 초기화 단계 (Initialization phase)
  3. 할당 단계 (Assignment phase)

선언 단계에서 변수를 실행 컨텍스트에 등록한다. 해당 변수는 스코프가 참조하는 대상이 된다.

다음으로 변수 객체에 등록된 변수를 위해서 메모리를 할당한다. 이 과정에서 변수는 undefined 으로 초기화된다.

마지막으로 undefined 로 초기화된 변수에 실제 값을 할당한다.

(* var 키워드로 선언한 변수는 선언과 초기화가 한번에 이루어진다.)

let 키워드로 선언한 변수는 선언과 초기화 과정이 분리되어 이루어진다. 즉 선언은 했지만, 아직 초기화가 이루어지지 않은 단계에서 변수에 접근하면 메모리가 아직 할당되기도 전 이므로 참조 에러가 발생한다. 초기화 이전까지는 메모리가 할당되기 이전 시점이며 당연하게도 변수를 참조할 수 없고, 이를 TDZ(Temporal Dead Zone) 라고 한다.

스코프 체인

출처는 여기

수백, 수천 개의 변수들이 유효한 범위, 정확히 원하는 시점에 메모리에 적재되고 사라지는 걸 지정하는 것이 중요하다.

scope chain 이라는 단어 자체에 의미를 전부 내포하고 있다. scope 는 변수 참조값의 경계를 담고 있는 박스이다. 이 박스를 전역 -> 지역으로 계층이 존재하는 형태로 참조된다.

  • brower 에선 window
  • Node 에서는 global

image

1

2

또한 중요한 점은 상위 박스를 참조할 수는 있지만, 하위 박스를 참조할 수 없다는 것이다. 이건 Java 의 상속과 동일하다.

image

global scope, local scope 와 block scope 도 존재한다. 아래 그림을 보면 scope 가 어떻게 이루어지는 지 알 수 있다.

image

호출 스택

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 프로퍼티를 소유하게 된다.

한방 예제

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()

image

image

Q. 그래서 Prototype 이 뭔지 왜 알아야 하는가?

출처

non-static 메서드를 만들고자 사용된다.

화살표 함수

출처는 이곳

This post is licensed under CC BY 4.0 by the author.