[Core JavaScript] | April 20, 2021
Core JavaScript(코어 자바스크립트) 내용 정리
Execution context : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
JS의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념
클로저를 지원하는 대부분의 언어에서 이와 유사하거나 동일한 개념이 적용되어 있음
Execution context / Call stack
//[1]
var a = 1;
function outer(){
function inner(){
console.log(a); //undefined
var a = 3;
}
inner(); //[2]
console.log(a); //1
}
outer(); //[3]
console.log(a); //1
이렇게 어떤 execution context가 활성화될 때, JS 엔진은 해당 context에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 execution context 객체에 저장
이 객체는 JS 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인할 수는 없음
활성화된 execution context의 수집 정보
VariableEnvironment
: 현재 context 내의 식별자들에 대한 정보(environmentRecord) + 외부 환경 정보(outerEnvironmentReference)LexicalEnvironment
: 처음에는 VariableEnvironment와 같지만, 변경 사항이 실시간으로 반영ThisBinding
: this 식별자가 바라봐야 할 대상 객체코드가 실행되지 전임에도 JS 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있다는 것
hoisting : ‘JS 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다’고 간주하는 것
식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다
(실제 엔진은 이러한 변환 과정을 거치지는 않음)원본 코드
function a(x){
console.log(x); //1
var x;
console.log(x); //1
var x = 2;
console.log(x); //2
}
a(1)
arguments를 변수 선언/할당과 같다고 간주
function a(){
var x = 1;
console.log(x); //1
var x;
console.log(x); //1
var x = 2;
console.log(x); //2
}
a()
hoisting
environmentRecord
는 현재 실행될 컨텍스트의 대상 코드 내 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없음
따라서 호이스팅할 때 변수명만 끌어올리고 할당 과정은 원래 자리에 남겨둠
function a(){
var x;
var x;
var x;
x = 1;
console.log(x); //1
console.log(x); //1
x = 2;
console.log(x); //2
}
a()
원본 코드
function a(){
console.log(b); //b함수
var b = 'bbb';
console.log(b); //'bbb'
function b(){}
console.log(b); //'bbb'
}
a()
hoisting
변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면, 함수 선언은 함수 전체를 끌어올림
(JS를 유연하고 배우기 쉬운 언어로 만들고자 하여, 함수를 선언한 위치와 무관하게 그 함수를 실행할 수 있게 되었지만 더 많은 혼란을 야기하기도 함)
function a(){
var b; //선언부만 끌어올림
function b(){} //함수 전체를 끌어올림
console.log(b); //b함수
b = 'bbb';
console.log(b); //'bbb'
console.log(b); //'bbb'
}
a()
함수 선언문을 함수 표현식으로 변경
function a(){
var b;
var b = function b(){}
console.log(b); //b함수
b = 'bbb';
console.log(b); //'bbb'
console.log(b); //'bbb'
}
a()
함수 선언문(function declaration): function 정의부만 존재하고 별도의 할당 명령이 없음
반드시 함수명이 정의되어야 함
함수 표현식(function expression): 정의한 function을 별도의 변수에 할당
함수명을 정의한 표현식은 ‘기명 함수 표현식’, 정의하지 않은 것을 ‘익명 함수 표현식’이라고 부름
일반적으로 함수 표현식은 ‘익명 함수 표현식’을 말함
//function declaration
function a(){}
a();
//(익명) function expression
var b = function (){}
b();
//기명 function expression
var c = function d(){}
c();
d(); //에러! 함수명은 오직 함수 내부에서만 접근 가능
원본코드
console.log(sum(1, 2)); //3
console.log(multiply(3, 4)); //Uncaught Type Error: multiply is not a function
//function declaration
function sum(a, b){
return a + b;
}
//function expression
var multiply = function(a, b){
return a * b;
}
hoisting
var sum = function sum(a, b){ //함수 선언문 전체를 끌어올림
return a + b;
}
var multiply; //변수는 선언부만 끌어올림(함수도 하나의 값으로 취급)
console.log(sum(1, 2)); //3
console.log(multiply(3, 4)); //Uncaught Type Error: multiply is not a function
multiply = function(a, b){ //변수의 할당부는 원래 자리에 남겨둠
return a * b;
}
hoisting-2
var sum;
var multiply;
sum = function sum(a, b){
return a + b;
}
console.log(sum(1, 2)); //3
console.log(multiply(3, 4)); //Uncaught Type Error: multiply is not a function
multiply = function(a, b){
return a * b;
}
함수 선언문(function declaration)으로 함수들을 작성한다면 선언된 함수들이 전부 가장 위로 끌어올려진다. (함수들이 얼마나 멀리 떨어져있느냐에 상관없이)
동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값이 먼저 할당한 값을 override 한다.
즉, 코드를 실행하는 중에 실제로 호출되는 함수는 맨 마지막에 선언된 함수뿐이다.
⇒ 함수 표현식(function expression)을 사용하면, 함수 선언 및 할당 코드 이전에 함수를 호출할 수 없으므로 상대적으로 안전하다.
전역공간에 함수를 선언하거나 동명의 함수를 중복 선언하는 경우는 없어야 한다.
만에 하나 전역공간에 동명의 함수가 여럿 존재하는 상황이라 하더라도, 모든 함수가 함수 표현식으로 정의되어 있는 것이 상대적으로 안전하다.
outerEnvironmentReference
outerEnvironmentReference는 현재 호출된 함수가 선언
될 당시의 LexicalEnvironoment를 참조
오직 자신이 선언된 시점의 LexicalEnvironment만 참조하므로 가장 가까운 요소부터 차례대로만 접근 가능
⇒ 무조건 scope chain 상에서 가장 먼저 발견된 식별자에만 접근 가능
코드 상에서 어떤 변수에 접근할 때:
현재 context의 LexicalEnvironment를 탐색
⇒ 발견하면 그 값을 반환
⇒ 발견하지 못할 경우 다시 outerEnvironmentReference
에 담긴 LexicalEnvironment 탐색
⇒ 전역 컨텍스트의 LexicalEnvironment까지 탐색해도 해당 변수를 찾지 못하면 undefined
반환
Scope chain example
변수 은닉화(variable shadowing): inner 함수 내부에서 a 변수를 선언했기 때문에, 전역 공간에서 선언한 동일한 이름의 a 변수에는 접근 불가
var a = 1;
var outer = function(){
var inner = function(){
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
>>>
--------------------------------------------------------------------------------------------
| | global context | outer context | inner context |
--------------------------------------------------------------------------------------------
| Lexical- | environmentRecord | a, outer | inner | a |
| Environment |---------------------------|----------------|--------------------------------
| (L.E) | outerEnvironmentReference | | global L.E | outer L.E |
--------------------------------------------------------------------------------------------
스코프 체인 확인 (브라우저의 개발자 도구 콘솔)
var a = 1;
var outer = function(){
var b = 2;
var inner = function(){
console.log(b);
debugger; //`console.dir(inner)` 도 가능
};
inner();
};
outer();
실행 컨텍스트의 thisBinding
에는 this로 지정된 객체가 저장됨
실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장됨
그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다름 (Chapter 3 참조)
VariableEnvironment
, LexicalEnvironment
, ThisBinding
의 세 가지 정보를 수집VariableEnvironment
와 LexicalEnvironment
의 구성environmentRecord
+ 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outEnvironmentReference