[NHN FORWARD 2021] 결국 자바스크립트를 알아보기로 했다.

[NHN FORWARD 2021] 결국 자바스크립트를 알아보기로 했다.

자바스크립트의 흐름

1. 실행컨텍스트

실행컨텍스트는 코드의 실제 진행 상황을 추적하는데 필요한 정보들을 모아둔 구조이다.

function sum(a, b) { let result = a + b; return result; } let number = sum(1, 2);

1. 자바스크립트가 로드되고 엔진이 이를 처리하면서 실행컨텍스트라는 것을 만드는데, 맨 처음에 전역 실행 컨텍스트와 그에 딸린 전역 메모리가 실행된다.

2. 전역에 선언된 함수나 변수들을 전역 실행 컨텍스트의 메모리에 등록한다.

함수 sum과 변수 result(sum의 결과값)가 등록된다.

전역 실행 컨텍스트 전역 메모리 sum : Fn

number :

3. 함수명 뒤에 () 소괄호를 붙여서 함수를 호출하고(함수의 실행컨텍스트 생성), a와 b 매개변수에 1과 2가 할당된다.

전역 실행 컨텍스트 전역 메모리 sum(1, 2);

함수 실행 컨텍스트 지역 메모리 a : 1

b : 2 sum : Fn

number :

4. result 변수를 만들고 a + b를 연산해 3이란 결과를 result에 할당한다.

전역 실행 컨텍스트 전역 메모리 sum(1, 2);

함수 실행 컨텍스트 지역 메모리 result = a + b; a : 1

b : 2

result : 3 sum : Fn

number :

5. 마지막으로 result를 반환하면 number에 result 값인 3이 할당된다.

전역 실행 컨텍스트 전역 메모리 sum(1, 2);

함수 실행 컨텍스트 지역 메모리 result = a + b; a : 1

b : 2

result : 3 sum : Fn

number : 3

6. sum 함수가 종료되면 함수의 실행컨텍스트와 그 내부 정보들을 사라진다.

전역 실행 컨텍스트 전역 메모리 sum : Fn

number : 3

-> 함수 뒤에 괄호를 붙여 서브 루틴이 호출되면 실행 컨텍스트가 만들어지고 해당 함수가 끝나면 실행컨텍스트가 사라진다.

2. 콜 스택

콜 스택은 현재 실행되고 있는 실행 컨텍스트를 추적하기 위한 구조체이다.

function sum(c, d) { let r = c + d; return r; } function calc(a, b, expr) { let result = expr(a, b); return result; } let number = calc(1, 2, sum);

1. 함수 sum 선언 후 sum 라벨에 함수바디를 연결하고 함수 calc 선언 후 calc 라벨에 함수바디를 연결한다.

그리고 변수 number(calc의 리턴값)를 만든다.

전역 실행 컨텍스트 전역 메모리 sum : Fn

calc : Fn

number :

2. calc를 소괄호로 호출하면 함수의 실행 컨텍스트가 새로 만들어진다.

calc라는 함수 실행 컨텍스트가 콜 스택에 쌓이고 a, b, expr 매개변수에 각 값을 연결해 준다.

a는 1, b는 2, expr은 sum의 함수바디가 값으로 연결된다.

전역 실행 컨텍스트 전역 메모리 calc(1, 2, sum);

함수 실행 컨텍스트 지역 메모리 a : 1

b : 1

expr : Fn sum : Fn

calc : Fn

number :

3. 함수 calc의 내부 코드를 실행해 준다. 변수 result를 만들고 expr이 소괄호로 호출되어서 함수 실행컨텍스트가 새로 만들어진다. 2번과 같이 콜스택에 expr 함수가 쌓이고 c와 b의 값이 1과 2가 된다.

전역 실행 컨텍스트 전역 메모리 calc(1, 2, sum);

함수 실행 컨텍스트 지역 메모리 expr(a, b);

함수 실행 컨텍스트 지역 메모리 c : 1

d : 2 a : 1

b : 1

expr : Fn

result : sum : Fn

calc : Fn

number :

4. 함수 sum에서 변수 r을 만들고 c + d를 연산해 변수 r에 연산한 값 3을 할당해준다. r은 result 값에 할당된다.

전역 실행 컨텍스트 전역 메모리 calc(1, 2, sum);

함수 실행 컨텍스트 지역 메모리 expr(a, b);

함수 실행 컨텍스트 지역 메모리 c + d c : 1

d : 2

r : 3 a : 1

b : 1

expr : Fn

result : 3 sum : Fn

calc : Fn

number :

5. expr의 실행 컨텍스트가 종료되고 콜 스택에서도 pop 되어 사라진다. result가 반환되어 3이 number에 할당된다.

전역 실행 컨텍스트 전역 메모리 calc(1, 2, sum);

함수 실행 컨텍스트 지역 메모리

a : 1

b : 1

expr : Fn

result : 3 sum : Fn

calc : Fn

number : 3

6. calc의 실행 컨텍스트가 종료되고 콜 스택에서도 pop 되어 사라진다.

전역 실행 컨텍스트 전역 메모리

sum : Fn

calc : Fn

number : 3

-> 콜 스택의 바닥엔 전역 컨텍스트가 존재한다. 함수가 호출될 때 해당 함수의 실행 컨텍스트가 Push되고 함수가 종료되면 Pop된다.

3. 스코프

스코프는 현재 접근할 수 있는 변수들의 범위이다.

var spreadRatio = 1.25; function getMortageRatio(fb) { var total = spreadRatio + fb; return total; } var ratio = getMortageRatio(2);

1. 전역 메모리에 변수 spreadRatio를 만들고 1.25 값을 할당해준다.

getMortageRatio 함수를 선언 후 라벨에 함수 바디를 연결한다. 변수 ratio를 만들어준다.(getMortageRatio의 리턴값)

전역 실행 컨텍스트 전역 메모리

spreadRatio : 1.25

getMortageRatio : Fn

ratio :

2. getMortageRatio를 호출하면 실행컨텍스트를 만들고 콜 스택에 Push 해준다. 매개변수 fb에 2를 할당해준다.

전역 실행 컨텍스트 전역 메모리 getMortageRatio(2);

전역 실행 컨텍스트 지역 메모리

fb : 2 spreadRatio : 1.25

getMortageRatio : Fn

ratio :

3. 변수 total을 만들어주고 spreadRatio + fb를 연산해주는데 spreadRatio가 함수 실행 컨텍스트의 메모리(지역 메모리)에 없기에 전역 메모리에 올라가 spreadRatio를 찾아 값을 연산해 3.25를 total 값에 할당해준다.

전역 실행 컨텍스트 전역 메모리 getMortageRatio(2);

전역 실행 컨텍스트 지역 메모리 total = spreadRatio + tb

fb : 2

total : 3.25 spreadRatio : 1.25

getMortageRatio : Fn

ratio :

4. total 값을 리턴해 변수 ratio 값에 할당해주고 getMortageRatio 함수의 실행이 종료되어 콜 스택에서 pop되어 빠진다.

전역 실행 컨텍스트 전역 메모리 spreadRatio : 1.25

getMortageRatio : Fn

ratio : 3.25

-> 현재 실행 중인 실행 컨텍스트에서 변수를 찾을 수 없다면, 이전 실행 컨텍스트로 탐색 범위를 옮긴다.

이를 스코프 체인(Scope Chain)이라 한다.

4. 클로저

클로저는 함수가 함수를 반환할 때, 반환되는 함수는 자신을 둘러싼 메모리 환경을 가지고 반환된다.

function outer() { var n = 0; function increase() { n += 1; } return increase; } var newFn = outer(); newFn(); newFn();

1. 함수 outer를 선언 후 라벨에 함수바디를 연결한다. 변수 newFn을 만들어주고 outer를 호출한다.

호출된 outer 함수는 콜 스택에 Push 된다.

전역 실행 컨텍스트 전역 메모리 outer();

전역 실행 컨텍스트 지역 메모리

outer : Fn

newFn :

2. 변수 n이 0으로 할당되고 함수 increase를 선언 후 라발에 함수바디를 연결하고 increase 함수 본체를 반환한다.

newFn이란 라벨에 increase와 똑같은 함수바디가 연결된다.

전역 실행 컨텍스트 전역 메모리 outer();

전역 실행 컨텍스트 지역 메모리

n : 0

increase : Fn outer : Fn

newFn : Fn

3. 함수 outer가 종료되면서 실행컨텍스트가 삭제되고 newFn을 ()로 실행해 함수로 호출한다.

전역 실행 컨텍스트 전역 메모리 newFn();

전역 실행 컨텍스트 지역 메모리

outer : Fn

newFn : Fn

4. 함수 increase 내부 코드 n += 1 연산을 실행하지만 함수 실행 컨텍스트의 지역 메모리에 n이 없기에 전역 메모리로 올라가지만 이미 outer 함수는 실행컨텍스트가 삭제되어 콜 스택에서 pop되었기에 원래라면 n을 찾을 수 없지만 n은 newFn의 값에서 발견된다.

엔진은 지역 메모리에서 값을 찾지 못했을 경우, 상위 컨텍스트로 스코프 체이닝을 이어가는데 그 전에 클로저를 먼저 확인하고 넘어간다.

n을 찾았으니 n은 1이 되고 실행컨텍스트가 종료되어 콜 스택에서 빠지게된다.

5. 다시 newFn() 이 호출되면 4번과 같은 절차를 밟게 되고 n은 1이기에 다시 + 1을 해주어서 2가 된다.

실행컨텍스트가 종료되어 콜 스택에서 빠지게 된다.

-> n은 newFn이 호출되어야만 접근이 가능한 변수가 된다.

-> 함수가 함수를 반환할 때, 반환되는 함수는 자신을 둘러싼 메모리 환경을 가지고 반환된다.

함수의 호출이 아닌 정의된 위치에 결정되는 스코프이며 렉시컬 스코프라 한다.

상위 실행 컨텍스트로 스코프 체인을 이어가기전 클로저에 변수가 있는지 먼저 확인한다.

클로저 변수는 함수가 호출되어야만 접근이 가능하기 때문에 정보 은닉에 활용된다.

함수 호출 간 공유 메모리로도 활용이 가능하다.

일반적으로 함수가 종료되면 메모리 환경이 사라져, 각 호출 간 연결고리가 없다.

5. 비동기 자바스크립트

자바스크립트 엔진은 기본적으로 싱글 스레드이다. 개발자들은 브라우저 API를 활용하여 비동기 프로그래밍을 할 수 있다.

// 각 테스크의 최소 처리 시간은 1ms function greet() { console.log("Hi"); } function wait(ms) { // blocking for ms } setTimeout(greet, 5); wait(1000); console.log("Bye");

1. 함수 greet를 선언 후 라벨에 함수바디를 연결한다. (1ms)

2. 함수 wait를 선언 후 라벨에 함수바디를 연결한다. 매개변수 만큼의 ms 시간을 끌어주는 함수이다. (2ms)

3. setTimeout 이라는 브라우저 API를 호출한다. 첫번째 매개변수는 콜백함수고, 두번째 매개변수는 지연시간이다.

setTimeout 호출이 종료되고 브라우저 API에 타이머가 하나 생긴다. 그 콜백으로 greet가 등록된다. (3ms)

4. wait이 호출되면 함수 실행 컨텍스트가 실행되고 매개변수 ms에 1000이 할당된다. (4ms)

실제 함수 바디를 수행하면서 1000ms (1초) 만큼 시간을 끌어준다. (5ms)

5. 8ms 가 되면 3ms에서 만들어진 타이머가 종료되지만 wait가 하던 일을 중간에 끊고 greet 콜백을 호출하지 않고 스레드 큐에 보내서 잠시 대기시킨다. (9ms)

6. 1000ms 작업을 마치고 실행컨텍스트가 종료된다. (1004ms)

스레드 큐에 보냈던 콜백을 바로 실행하지는 않고 전역 컨텍스트에서 처리해야할 것들을 먼저 처리한다. (1005ms)

7. 더 이상 실행할 것이 없다면 엔진은 큐에서 콜 스택으로 콜백 함수를 옮겨준다. (1006ms)

함수 실행 컨텍스트를 만들면서 greet를 실행해준다. (1007ms)

그리고 함수 실행 컨텍스트를 종료한다. (1008ms)

-> 브라우저 API에서 콜백을 스레드 큐에 등록시킨다.

엔진이 콜백을 실행할 준비가 되면 스레드 큐에서 콜 스택에서 콜백 함수를 넘겨준다.

콜백을 실행할 준비가 되는 시점은 콜 스택이 비어있고 전역 실행 컨텍스트에서 실행할 코드가 없을 때다.

-> 조건을 계속 체크하고 콜백 함수를 큐에서 스택으로 옮겨주는 걸 이벤트 루프라 한다.

6. 자바스크립트의 프로토타입

자바스크립트는 프로토타입이라는 특수한 객체를 가지고 있다.

let fns = { getName() { return this.name; } , addAge() { this.age += 1; } }; function createPerson(name) { let newPerson = {}; Object.setPrototypeOf( newPerson, fns ); newPerson.name = name; newPerson.age = 0; return newPerson; } let john = createPerson('john'); john.getName();

1. 변수 fns라는 라벨을 만들고 객체 전체를 연결한다. 함수 createPerson을 선언 후 라벨에 함수 바디를 연결해준다.

변수 john을 만든다. (createPerson의 리턴값)

전역 실행 컨텍스트 전역 메모리 fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

2. 함수 createPerson를 호출하고 name 매개변수에 john을 할당해준다.

전역 실행 컨텍스트 전역 메모리 createPerson();

함수 실행 컨텍스트 지역 메모리 name : 'john' fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

3. 함수 createPerson 내부 바디를 실행한다. 변수 newPerson에 빈 객체를 할당한다.

Object.setPrototypeOf 함수를 이용해 이 객체의 프로토타입을 fns 객체로 연결해준다.

fns를 참조하기 위한 라벨이 필요하기 때문에 __proto__ 라는 특수 속성을 붙여준다.

전역 실행 컨텍스트 전역 메모리 fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

4. 함수 createPerson를 호출하고 name 매개변수에 john을 할당해준다.

전역 실행 컨텍스트 전역 메모리 createPerson();

함수 실행 컨텍스트 지역 메모리 Object.setPrototypeOf(); name : 'john'

newPerson : {

__proto__ :

} fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

5. newPerson의 name에 매개변수 name을 할당해준다.

전역 실행 컨텍스트 전역 메모리 createPerson();

함수 실행 컨텍스트 지역 메모리 newPerson.name = name; name : 'john'

newPerson : {

name : 'john'

__proto__ :

} fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

6. newPerson의 age 속성을 만들고 0을 할당해 준다.

newPerson은 age와 name 속성을 가지고 있고, __proto__ 라는 속성을 통해 fns로의 연결을 가진다.

전역 실행 컨텍스트 전역 메모리 createPerson();

함수 실행 컨텍스트 지역 메모리 newPerson.age = 0; name : 'john'

newPerson : {

name : 'john'

age : 0

__proto__ :

} fns : getName : Fn

addAge : Fn

createPerson : Fn

john :

7. newPerson 객체가 반환되어 변수 john에 할당된다.

전역 실행 컨텍스트 전역 메모리 createPerson();

함수 실행 컨텍스트 지역 메모리 name : 'john'

newPerson : {

name : 'john'

age : 0

__proto__ :

} fns : getName : Fn

addAge : Fn

createPerson : Fn

john : {

name : 'john'

age : 0

__proto__ :

}

8. 함수 createPerson가 실행 컨텍스트도 날아간다. john에 getName이 있는지 찾지만 객체 자체에 getName이란 속성이 없기에 엔진은 __proto__를 참조한다.

fns에 __proto__ 속성을 연결해주었기에 연결된 john에서 getName을 찾는다.

getName은 함수로 호출되기에 함수 실행 컨텍스트가 만들어지고 이름을 반환하며 종료된다.

-> 인스턴스 생성을 위해 빈 객체를 만들고 프로토 타입을 연결해준 뒤 속성 값들을 할당해주고 반환한다.

-> new 키워드가 내부적으로 해주는 일

> 위의 것을 자동으로 반환해 주는 new 키워드 사용해서 인스턴스를 만드는 과정 (위의 과정과 같음)

function newCreatePerson(name) { this.name = name; this.age = 0; } newCreatePerson.prototype.getName = function() {return this.name; }; newCreatePerson.prototype.addAge = function() {return this.age += 1; }; let newJohn = new newCreatePerson('John'); newJohn.getName();

Reference

[NHN FORWARD 2021] 결국 자바스크립트를 알아보기로 했다 : https://www.youtube.com/watch?v=HoqMPUkzMSA

from http://relaxed-it-study.tistory.com/287 by ccl(A) rewrite - 2021-12-22 19:28:06