
자바스크립트 스펙에도 명시 되어 있어, 이를 이해하면 자바스크립트의 동작을 스펙 레벨에서 이해하고 활용할 수 있다.
자바스크립트 실행 환경에는 브라우저 환경, node.js를 이용한 로컬 환경 등이 있음
이 실행 환경에서 공통적으로 벌어지는 일을 확인해보자.
// 어떤 코드도 없는 경우
HTML, CSS로만 구성된 페이지를 브라우저에서 로드했을 때가 있다.
자바스크립트 엔진은 어떤 코드가 없어도 다음 세가지 변수를 초기화 한다.
👩💻 정리!
- 자바스크립트 엔진은 코드가 없어도 실행 환경(실행 컨텍스트)를 초기화한다.
- 스코프(scope)는 코드가 현재 실행되는 환경, 맥락(context)을 의미한다.
- this 포인터, 스코프에 저장된 변수들, 스코프 체인 등이 환경에 포함된다.
- this 포인터의 경우, 글로벌 스코프에서는 window를 가리킨다.
this 포인터라고 표현을 했는데, 자바스크립트에서는 레퍼런스 변수로 이해하면 된다.
function myFunc() {
let a = 10;
let b = 10;
function add(first, second) {
return first + second;
}
return add(a, b);
}
myFunc();


글로벌 컨텍스트의 VO에는 myFunc가 포함된다.
myFunc 함수가 실행되었을 때 myFunc 실행컨텍스트가 새롭게 만들어진다. 기존의 글로벌 컨텍스트는 그대로 존재한다.
myFunc 함수가 종료되면 맨 위의 실행컨텍스트가 제거되고, 모든 코드가 끝나면 글로벌 컨텍스트도 제거된다.
이는 스택 구조로 구성되어있다.
call stack : 실행컨텍스트가 쌓이면서 구성되는 것
자바스크립트가 strict mode로 실행되었을 때 myFunc의 this는 undefined가 된다.
스코프체인
만약 다른 변수를 찾아야 할 경우, 스코프 체인을 따라 변수를 검색하는 용도로 활용된다.
👩💻정리!
- 함수가 실행되면, 함수 스코프에 따라 환경이 만들어진다.
- this, 함수 스코프의 변수들, 그리고 스코프 체인이 형성된다.
- 스코프 체인을 따라 글로벌 환경에 도달한다.
let o = {
name: 'Daniel',
method: function (number) {
return this.name.repeat(number);
},
};
function myFunc() {
let n = 10;
return o.method(n);
}
myFunc();

👩💻 정리!
- 객체의 메서드의 경우, 메서드 환경의 this는 해당 객체를 가리키게 된다.
- 하지만 this가 가리키는 것은 환경에 따라 변할 수 있다.
let a = 10;
function f1() {
let b = 20;
function print(v) {
console.log(v);
}
function f2() {
let c = 30;
print(a + b + c);
}
f2();
}
f1();

console 은 VO 안이 아니라 윈도우 객체 밑에 있는 변수지만 전역에서 참조할 수 있는 변수라 편의상 같이 표기
아래서부터
전역 - f1 - f2 - print (console은 전역에서 참조)
콜스택
함수가 끝나는대로 실행 컨텍스트 스택 위에서부터 제거된다. 가장 마지막에 쌓인 것이 가장 먼저 제거되는 구조이다.
실행 컨텍스트의 종류
가장 최상위 스코프에는 글로벌 스코프가 있고, 자바스크립트가 처음 실행될 때, 또는 모든 함수 호출이 끝났을 때 자바스크립트는 전역 실행 컨텍스트로 만들어지고, 되돌아온다.
함수가 실행 될 때는 함수 실행 컨텍스트가 만들어진다.
전역에 존재하는 코드는 함수나 클래스 내부의 코드를 무시하고 컨텍스트를 가진다.
함수에 존재하는 코드는 함수 내부에서만 컨텍스트를 가진다.
식별자와 식별자에 연결된 값을 저장하는 자료구조
const myName = "Elice";
function Coder() {
const title = "Coder Land";
return title;
}
전역으로 생성되는 렉시컬 환경에서는 myName, Coder 이렇게 2개의 식별자와 식별자에 연결된 값이 저장된다.
myName에는 Elice가 들어있지만, Coder는 스코프 체인으로 Coder와 연결되어 있다.
Coder함수의 렉시컬 환경에서는 title이라는 1개의 식별자와 식별자에 연결된 값이 저장된다. 또한, 전역 렉시컬 환경에 스코프 체인으로 연결되어 있다. 전역에서는 Coder라는 정보를 갖고 있기 때문이다.
함수가 호출되는 상황은 4가지가 있다
function myFunc() {
console.log('myFunc called');
}
myFunc(); // 직접 호출 // this는 window를 가리킴
const o = {
name: 'Daniel',
printName: function () {
console.log(this.name);
},
};
o.printName(); // 메서드 호출 // this는 메서드를 호출한 객체를 가리킴, 하지만 함수의 내부에 또 함수를 만들고 this를 호출하면 동적 바인딩으로 인해 전역 객체를 가리킴
function Person(name) {
this.name = name;
this.printName = function () {
console.log(this.name);
};
}
const p = new Person('Daniel'); // 생성자 호출
setTimeout(p.printName.bind(p), 1000); // 간접 호출
let o = {
name: 'Daniel',
f1: () => {
console.log('[f1] this : ', this);
},
f2: function () {
console.log('[f2] this : ', this);
},
};
o.f1(); // global
o.f2(); // o
setTimeout(o.f1, 10); // global
setTimeout(o.f2, 20); // global
let o = {
name: 'Daniel',
printName: function () {
console.log('내 이름은 ', this.name);
},
};
o.printName(); // 내 이름은 Daniel
setTimeout(o.printName, 10); // 내 이름은 undefined
setTimeout(o.printName.bind(o), 20); // 내 이름은 Daniel
일반 함수의 경우, 화살표 함수와 다르게 bind로 this를 변경할 수 있다.
화살표 함수로 고정하기
생성자 함수로 만든 객체, 객체 리터럴 방식으로 만든 객체 모두 내부에서 this가 자기 자신을 가리킨다. 하지만 두 경우 모두 내부에서 함수를 만들고 해당 함수에서 this를 출력하면 window를 가리킨다.
/* 생성자 함수 방식 */
function createObject() {
this.myFunc = function () {
console.log("myFunc this:", this);
return function () { console.log("myFunc return this:", this) };
};
}
const o = new createObject();
o.myFunc()();
// myFunc this: createObject {...}
// myFunc return this: window {...}
/* 객체 리터럴 방식 */
const o = {
myFunc: function () {
console.log("myFunc this:", this)
return function () { console.log("myFunc return this:", this) }
},
};
o.myFunc()();
// myFunc this: myFunc {...}
// myFunc return this: window {...}
화살표 함수를 사용하면 객체 최상위 스코프를 가리키도록 유지할 수 있다.
/* 생성자 함수 방식 */
function createObject() {
this.myFunc = function () {
console.log("myFunc this:", this);
// 다음 부분을 화살표 함수로 변경
return () => { console.log("myFunc return this:", this) };
};
}
const o = new createObject();
o.myFunc()();
// myFunc this: createObject {...}
// myFunc return this: createObject {...}
/* 객체 리터럴 방식 */
const o = {
myFunc: function () {
console.log("myFunc this:", this)
// 다음 부분을 화살표 함수로 변경
return () => { console.log("myFunc return this:", this) };
},
};
o.myFunc()();
// myFunc this: myFunc {...}
// myFunc return this: myFunc {...}
bind
/* 생성자 함수 방식 */
function createObject() {
this.myFunc = function () {
console.log("myFunc this:", this);
return function () { console.log("myFunc return this:", this) };
};
}
const o = new createObject();
o.myFunc().bind(o)(); // bind 메서드를 사용해 o객체로 고정시킵니다.
// myFunc this: createObject {...}
// myFunc return this: createObject {...}
/* 객체 리터럴 방식 */
const o = {
myFunc: function () {
console.log("myFunc this:", this)
return function () { console.log("myFunc return this:", this) }
},
};
o.myFunc().bind(o)(); // bind 메서드를 사용해 o객체로 고정 그리고 함수를 실행
// myFunc this: myFunc {...}
// myFunc return this: myFunc {...}
apply, call
call은 인수 목록을 받고, apply는 인수 배열을 하나 받는다.
ex) call(this, var1, var2, var3, …) / apply(this, [ el, el2, el3, … ])
/* 생성자 함수 방식 */
function createObject() {
this.myFunc = function () {
console.log("myFunc this:", this);
return function () { console.log("myFunc return this:", this) };
};
}
const o = new createObject();
o.myFunc().call(o, null); // call 메서드를 사용해 o객체로 고정시킨 후 함수 실행
// myFunc this: createObject {...}
// myFunc return this: createObject {...}
o.myFunc().apply(o, null); // apply 메서드를 사용해 o객체로 고정시킨 후 함수 실행
// myFunc this: createObject {...}
// myFunc return this: createObject {...}
/* 객체 리터럴 방식 */
const o = {
myFunc: function () {
console.log("myFunc this:", this)
return function () { console.log("myFunc return this:", this) }
},
};
o.myFunc().call(o, null); // call 메서드를 사용해 o객체로 고정시킨 후 함수 실행
// myFunc this: myFunc {...}
// myFunc return this: myFunc {...}
o.myFunc().apply(o, null); // apply 메서드를 사용해 o객체로 고정시킨 후 함수 실행
// myFunc this: myFunc {...}
// myFunc return this: myFunc {...}
const o = {
name: "Elice",
}
function myFunc() {
console.log(this);
}
myFunc() // window {...}
myFunc.bind(o)() // {name: 'Elice'}
myFunc.call(o, null) // {name: 'Elice'}
myFunc.apply(o, null) // {name: 'Elice'}
let o = {
method() {
console.log('context: ', this); // o
let f1 = function () {
console.log('[f1] this : ', this);
};
let f2 = () => {
console.log('[f2] this : ', this);
};
f1(); // global
f2(); // o
},
};
o.method();
👩💻정리!
- 화살표 함수의 this는 정해지면 바꿀 수 없다.
- call, bind, apply를 사용해도 바뀌지 않는다.
- setTimeout 등 this가 바뀌는 상황에서 유용하다.
let f = function () {}
const Person = (name) => {
// 함수를 변수로 생성한다.
const printName = () => console.log(name);
return { printName };
}; // 함수를 리턴하며 closure를 생성한다.
팩토리 함수 : 함수를 리턴하는 함수
function createCard() {
let title = '';
let content = '';
function changeTitle(text) {
title = text;
}
function changeContent(text) {
content = text;
}
function print() {
console.log('Title : ', title);
console.log('Content : ', content);
}
return { changeTitle, changeContent, print };
}
const card1 = createCard();
card1.changeTitle('생일 카드');
card1.changeContent('생일 축하해');
card1.print();
let rate = 1.05;
function app() {
let base = 10;
return function (price) {
return price * rate + base;
};
}
const getPrice = app();
getPrice(120); // 136
function createCard() {
let title= "";
let content= "";
function changeTitle(text) {
title= text
}
function changeContent(text) {
content= text
}
function print() {
console.log("TITLE -", title);
console.log("CONTENT -", content);
}
return { changeTitle, changeContent, print};
}
const myCard = createCard()로 인스턴스를 만들어 리턴되는 함수를 통해 수정할 수 있다.