You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: blog/development/javascript-engine-and-hoisting-closure/index.mdx
+52-11Lines changed: 52 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,8 +7,12 @@ date: 2019-02-22 02:33:46
7
7
updateDate:
8
8
deck: 자바스크립트를 처음 접하면, 굉장히 쉬운 사용성에 놀라곤한다. C 언어와 Java 를 접했을때는 매번 컴파일이 필요했는데, 자바스크립트는 지금 이 글을 보고있는 브라우저에 개발자 모드 콘솔창을 켜놓고 바로 코딩이 가능하다는것은 가벼움과 쉬운 사용성을 의미한다. 코드의 실행은 어떻게 자바스크립트 엔진(그 중 V8) 내에서 이뤄지는것인가? 이를 이해한다면 변수와 함수 관계에 해당하는 Hoisting(호이스팅), Closure(클로저) 개념을 단순이 암기가 아닌 원리로 이해할 수 있다.
9
9
abstract: 우리가 작성한 .js 파일의 실행을 담당하는 자바스크립트 엔진이란게 무엇인지 간단하게 살펴보고, 자바스크립트 엔진이 .js 파일을 어떻게 실행하는지를 이해한다. 그 다음 매번 외웠던 '호이스팅'과 '클로저' 개념을 다시 이해해본다.
@@ -77,7 +81,9 @@ ASTs, 바이트코드 캐싱에 대한 과정은 본 글의 취지와 맞지않
77
81
78
82
## Compilation Phase
79
83
80
-
Compilation Phase 를 이해가 쉽도록 말하자면 **Heap 에 적재하는 과정**이라 할 수 있겠다. Heap 은 앞서 설명하였듯이 함수 실행에 필요한 내부의 파라미터, 변수, 함수들을 (Stack 에 적재하는 언어와 달리) 자바스크립트 엔진이 적재하는 곳이다. 그렇기에 Heap 에는 함수 Context Scope 가 함수의 정의 개수 만큼 쌓인다고 설명하였다. 그리하여 Compilation Phase 는 함수의 정의 단위로 수행되며, 새로운 함수 호출이 발생하면 그 함수에 맞는 Local Execution Scope 영역을 생성하고, 같은 일을 반복한다.
84
+
Compilation Phase 를 이해가 쉽도록 말하자면 **Heap 에 적재하는 과정**이라 할 수 있겠다. Heap 은 앞서 설명하였듯이 함수 실행에 필요한 내부의 파라미터, 변수, 함수들을 (Stack 에 적재하는 언어와 달리) 자바스크립트 엔진이 적재하는 곳이다. 자바스크립트 실행할때 얼마나 많은 함수들이 정의되고 호출되는데, **그 많은 함수의 파라미터랑 함수 내 변수가 "하나의 Heap" 에 저장**된다고? 그러면 어떻게 구별할까?
85
+
86
+
함수가 실행될때(뒤에 배울 Execution Phase) 해당 함수에 대한 Scope 이 생기고, 함수 파라미터 및 함수 내 변수들은 이 Scope 에 컴파일 단계(Compilation Phase)에서 정의 및 적재된다. 파라미터, 변수의 Scope 은 모두 컴파일 단계에 정의되므로 후에 배울 Lexical Scope 이라한다.
81
87
82
88
-**Compilation Phase** 에선 **변수 선언** 및 **함수 선언** 만 **Heap 에 적재**한다.
83
89
@@ -187,7 +193,7 @@ f(1);
187
193
7.`f(1)`는 **함수 호출**이므로 `f()` 선언 여부를 확인하여 수행
188
194
-**Global Scope (window)** 영역에 함수 `f()` 존재
189
195
-`f()` 함수 실행을 위해 또 Compilation Phase 및 Execution Phase 이 필요하기에
190
-
- Heap 에 **새 Local Execution Scope** 영역을 생성한다.
196
+
- Heap 에 `f()` 를 위한 **새 Local Execution Scope** 영역을 생성한다.
191
197
- Scope Chain 을 위해 꼭 자신을 호출한 부모 함수 Scope 에 대한 포인터를 갖는다.
192
198
-`(hidden) A pointer for previous scope`
193
199
@@ -249,7 +255,9 @@ f(1);
249
255
250
256
## Lexical Scope
251
257
252
-
프로그래밍 언어는 변수 Scope 가 언제 정의되냐에 따라 Scope 명칭이 다르다. 컴파일 시점이면 **"Static Scope"**, 이는 Lexer, Parser 과정에 정의되는것이기에 **"Lexical Scope"** 라고도 부르고, 런타임 시점이면 **"Dynamic Scope"** 로 부른다. 자바스크립트 실행은 **함수 단위로 컴파일(Compilation Phase) 시점에 변수의 Scope 를 먼저 정의한 뒤에 + 수행(Execution Phase) 절차가 재귀적**으로 이뤄진다. 아래의 예를 보면 함수 `b()`와 변수 `var num = 1;`는 하나의 동일한 `main()` 함수의 Scope 인 **Global Scope (window)**에 정의된다. 그렇기에 함수 `b()`는 언제든지 변수 `var num = 1;`에 접근할 수 있는것이다.
258
+
프로그래밍 언어는 변수 Scope 가 언제 정의되냐에 따라 Scope 명칭이 다르다. 컴파일 시점이면 **"Static Scope"**, 런타임 시점이면 **"Dynamic Scope"** 로 부른다. 자바스크립트에선 **컴파일 단계(Execution Phase)**에서 파라미터, 변수 모두 **정의될 때** 그때 위치한 함수의 Scope 에 귀속되기 때문에 **"Lexical Scope"** 라고도 부른다. [Lexical 의미는 무엇인가 만드는 것, 만드는 시점을 뜻하며, 파라미터, 변수, 함수 모두 만들어질때 즉 정의될때 Scope 를 따른다는 점에서 Lexical Scope 이라 부르는것이다.](https://www.freecodecamp.org/news/javascript-lexical-scope-tutorial/) 그래서 Lexical Scope 을 Static Scope 으로 얘기할 수도 있다.
259
+
260
+
아래의 예를 보면 함수 `b()`와 변수 `var num = 1;`는 하나의 동일한 `main()` 함수의 Scope 인 **Global Scope (window)에서 정의된다**. 그렇기에 함수 `b()`는 언제든지 변수 `var num = 1;`에 접근할 수 있는것이다.
253
261
254
262
```Javascript
255
263
var num =1;
@@ -278,7 +286,11 @@ function b() {
278
286
a();
279
287
```
280
288
281
-
`a()`의 실행 결과는 `10`이 아닌 `1`이다. 이유는 `function b()`가 **"어디에서 호출되었는지" = "런타임"**시점이 중요한것이 아니라 **"어디에서 정의되었는지" = "컴파일(렉시컬)"**시점이 중요함을 상기하면 Global Scope 에 정의된 함수 `b()`는 Global Scope 에 정의된 `var num = 1;`을 바라보게 된다. 일반적으로 보편적 프로그래밍 언어인 런타임을 생각하여, `10`이라고 착각하게 되는것이다.
289
+
`a()`의 실행 결과는 `10`이 아닌 `1`이다. 이유는 `function b()`가
290
+
-**"어디에서 호출되었는지" = "런타임"**시점이 중요한것이 아니라
291
+
-**"어디에서 정의되었는지" = "컴파일(렉시컬)"**시점이 중요함을 상기하면
292
+
293
+
Global Scope 에 정의된 함수 `b()`는 Global Scope 에 정의된 `var num = 1;`을 바라보게 된다. 일반적으로 보편적 프로그래밍 언어에선 런타임 시 Scope 을 생각하여, `10`이라고 착각하게 되는것이다.
282
294
283
295
284
296
## Variable Shadowing
@@ -318,7 +330,7 @@ var functionExpression = function(x) { return x } // 함수 표현식
318
330
319
331
앞서 1) Lexical Scope 와 2) Hoisting 은 우리가 보편적으로 생각하는 방식과 다르기에 실수할것들이 많았다.
320
332
321
-
1. Lexical Scope + Scope Chain = Function-level Scope = 함수 외부에서 선언한 변수 모두에 접근 가능하다.
333
+
1.Function-level Scope(Lexical Scope + Scope Chain) = 함수 외부에서 선언한 변수 모두에 접근 가능하다.
322
334
```javascript
323
335
var a =1
324
336
@@ -351,12 +363,16 @@ const, let 의 등장으로 if, for 문과 같은 block-level({}) 단위 변수
351
363
352
364
## Garbage Collection
353
365
354
-
자바스크립트에서 매 새 함수를 호출할때마다 Heap 에 함수 단위의 Context Scope 생성됨이 이젠 머릿속에 박혀있을것이다. Context Scope 는 함수 호출에만 유효하기에, 해당 함수의 호출이 끝난다면 해당 함수의 Scope 는 Heap 에서 제거된다. 이를 메모리 청소의 의미로 Garbage Collection 이라 부른다. 자바스크립트 파일 실행이 모두 끝나면 가장 처음에 호출됐던 `main()` 함수도 끝이나고, 이에 Global Scope(Window) 도 사라지게 된다. 이렇게 **자바스크립트는 단순히 함수(포인터)의 Reachability 를 기반으로 Garbage Collection 를 수행**한다. Swift 라던가 Java 는 Reference Count 전략을 통한 Garbage Collection 를 수행하는것과 달리 단순한 전략을 취함만 알면 된다.
366
+
<GatsbyImage
367
+
alt="Function's Context Scope and Lexical Scope for variables"
368
+
image={getImage(props.localImages[0])}/>
369
+
370
+
자바스크립트에서 매 새 함수를 호출할때마다 Heap 에 함수 단위의 Context Scope 생성됨이 이젠 머릿속에 박혀있을것이다. Context Scope 는 함수 호출에만 유효하기에, 해당 함수의 호출이 끝난다면 해당 함수의 Scope 는 Heap 에서 제거된다. 이를 메모리 청소의 의미로 Garbage Collection 이라 부른다. 자바스크립트 파일 실행이 모두 끝나면 가장 처음에 호출됐던 `main()` 함수도 끝이나고, 이에 Global Scope(Window) 도 사라지게 된다. 이렇게 **자바스크립트는 단순히 함수(포인터)의 Reachability 를 기반으로 Garbage Collection 를 수행**한다. Swift 라던가 Java 는 Reference Count 전략을 통한 Garbage Collection 를 수행하는것과 달리 Mark And Sweep 이라는 단순한 전략을 취함만 알면 된다.
355
371
356
372
357
373
## Closure
358
374
359
-
Java 의 언어에서는 Class 를 통해 변수를 private 으로 선언하여 Encapsulation 을 이뤄낸다. 외부에서 클래스 내 변수에 접근을 금하며, 변수의 변경은 모두 public 으로 노출된 함수를 통해서만 가능하게 한다. 객체지향 프로그래밍(OOP)뿐만 아니라 행동 주도 개발(DDD)의 필수 개념이 Encapsulation 인데, 애석하게도 Javascript 의 Class 는 Java 의 Class 와 달리 Encapsulation 을 지원하지 않는다. 물론 `_` Prefix 가 붙은 변수를 암묵적으로 private 변수로 판단하는 컨벤션이 있었지만, 어쨌든 Object 의 Prototype 을 콘솔로 찍으면 다 보이게되어서 의미가 없다. 또한 최근 자바스크립트에서 클래스 변수 앞에 `#` Prefix 를 붙이면 유사 Private 처럼 동작함을 알았는데, [`#변수명` 으로 변수가 정의되는것](http://www.gisdeveloper.co.kr/?p=11697)이기에, 근본적인 해결책은 아니다.
375
+
Java 의 언어에서는 Class 를 통해 변수를 private 으로 선언하여 Encapsulation 을 이뤄낸다. 외부에서 클래스 내 변수에 접근을 금하며, 변수의 변경은 모두 public 으로 노출된 함수를 통해서만 가능하게 한다. 객체지향 프로그래밍(OOP)뿐만 아니라 행동 주도 개발(DDD)의 필수 개념이 Encapsulation 인데, 애석하게도 Javascript 의 Class 는 Java 의 Class 와 달리 Encapsulation 을 지원하지 않는다. 물론 `_` Prefix 가 붙은 변수를 암묵적으로 private 변수로 판단하는 컨벤션이 있었지만, 어쨌든 Object 를 콘솔로 찍으면 다 보이게되어서 의미가 없다. 또한 최근 자바스크립트에서 클래스 변수 앞에 `#` Prefix 를 붙이면 유사 Private 처럼 동작함을 알았는데, [`#변수명` 으로 변수가 정의되는것](http://www.gisdeveloper.co.kr/?p=11697)이기에, 이 또한 Object.getOwnPropertySymbols() 를 통해 다 보여서 근본적인 해결책은 아니다.
360
376
361
377
이때 자바스크립트에서는 Closure 를 사용하면 된다. 함수가 정의되는 Scope 에 따라 해당 함수가 참조할 수 있는 변수가 결정되는 Lexical Scope 를 활용하여, "함수의 정의" 자체를 반환하면 된다. 우선 Encapsulation 을 설명하기 전에 Closure 를 어떻게 정의하는지에 대해 알아보자.
362
378
@@ -381,9 +397,9 @@ var closureTest = function() {
381
397
};
382
398
```
383
399
384
-
이렇게 정의하는 방식을 Closure 라고 부른다.
400
+
이렇게 함수의 정의를 반환하는 방식을 Closure 라고 부른다.
385
401
386
-
Closure 를 사용하는 방식이었던, Encapsulation 은 여기서 우리가 private 로 가두고 싶은 변수를 정의만하면 된다.
402
+
함수가 정의되는 Lexical Scope 에 정의된 변수는 해당 정의된 함수에서 접근이 가능하다.는 점을 이용하여 우리가 private 로 가두고 싶은 변수를 Closure 함수 정의 내부에 정의하면된다. 이를 통해 Encapsulation 을 이뤄낼 수 있다.
387
403
388
404
```javascript
389
405
varclosureTest=function() {
@@ -399,7 +415,7 @@ closure(); // output: This is innerFunction
private 변수는 미공개 변수라고도 표현할 수 있다. 위의 예시는 미공개 변수는 고정된 값이었지만, 이를 상태로 정의할 수도 있다. Java 의 객체지향 프로그래밍에 충실한 방식의 코드 작성 기법이다. 또한 추가로 함수를 **하나**가 아닌 **여러개**를 정의해보자. Closure 설명에 정말 수도없이 인용되는 counter 함수를 정의해보자.
418
+
private 변수는 '미공개 변수'라고도 불린다. 위의 예시는 미공개 변수는 고정된 값이었지만, 이를 변경가능한 상태로 정의할 수도 있다. Java 의 객체지향 프로그래밍에 충실한 방식의 코드 작성 기법이다. 또한 추가로 함수를 **하나**가 아닌 **여러개**를 정의해보자. Closure 설명에 정말 수도없이 인용되는 counter 함수를 정의해보자. Closure 는 항상 함수를 반환하는것으로 생각할 수도 있는데, 다음과 같이 객체를 반환할 수 도 있다.
`counterClosure` 안의 `count` private 변수를 변경할 수 있는 방법은 함수를 호출하는 방법밖에 없고, 보기 위해서도 함수를 통해서만 볼 수 있다. `count` 변수는 `increase()`, `decrease()`, `show()` 함수를 통한다면 어디서든지 접근가능하다는 뜻이기도하다. 이 말은 즉슨, `count` 변수의 **Reachability**는 언제든 열려있단 뜻이며, 자바스크립트 엔진이 **Garbage Collection** 를 언제 수행해야할지 전혀 알 수 없다는 뜻이기도하다.
429
445
430
-
**Closure** 는 이와 같이 **Garbage Collection**되지 않는다는 치명적인 단점을 가지고있다. [**Garbage Collection**되게 하기 위해서는 `countCloure` 변수를 `null` 값을 넣어서 변수와 함수 모두에 대한 Refer 를 제거(Reachability 를 삭제)](https://dkje.github.io/2020/09/18/Closure/)하면 된다.
446
+
따라서 `counterClosure` 객체는 수행이 모두 끝나더라도, 메모리에서 삭제되지 않는다. `count` 변수는 언제든지 사용될 준비를 하고있다는것의 의미는 `count` 변수는 `counter` 함수 내 존재하지만 global 에 참조되어있다는 의미이기도 하다. 이에 `counter` 함수가 정의된 global scope 가 닫히기 전까지는 계속해서 존재하며, 메모리 누수가 발생하게된다.
447
+
448
+
**Closure** 는 이와 같이 **Garbage Collection**되지 않는다는 치명적인 단점을 가지고있지만, 해결을 위해선 [**Garbage Collection**되게 하기 위해서는 `countCloure` 변수를 `null` 값을 넣어서 변수와 함수 모두에 대한 Refer 를 제거(Reachability 를 삭제)](https://dkje.github.io/2020/09/18/Closure/)하면 된다.
449
+
450
+
```javascript
451
+
counterClosure =null;
452
+
```
453
+
454
+
다른 방식으로는 재사용성이 없는 함수의 경우엔 IIFE(Immedietely Invoked Function Expression) 를 사용하면 된다.
455
+
456
+
```javascript
457
+
(functionhello() { ... })();
458
+
```
459
+
460
+
```javascript
461
+
(functionmakePrivateFunc() {
462
+
constmessage="private data";
463
+
constprivateFunc=function () {
464
+
console.log(`${message} can also implemented through the IIFE`);
465
+
};
466
+
privateFunc();
467
+
})();
468
+
```
469
+
---
470
+
471
+
다음과 같이 자바스크립트 엔진의 동작은 '컴파일 - 실행' 2개 과정으로 이뤄지는것과 함께, 함수의 실행(Context Scope)와 변수의 정의(Lexical Scope)에 대해 간략하게 알아보았다. 더 디테일하게 들어가면 수도없이 복잡해지는데, 웹 어플리케이션 개발자로는 이러한 개념만 알아도 실개발과 면접에 큰 이슈는 없을것이라 생각한다.
0 commit comments