ES2015부터 class 키워드를 지원하기 시작했으나, 문법적인 양념일 뿐이며 자바스크립트는 여전히 프로토타입 기반의 언어다.
상속 관점에서 자바스크립트의 유일한 생성자는 객체뿐이다. 각각의 객체는 [[Prototype]]이라는 은닉(private) 속성을 가지는데 자신의프로토타입이 되는 다른 객체를 가리킨다. 그 객체의 프로토타입 또한 프로토타입을 가지고 있고 이것이 반복되다, 결국null을 프로토타입으로 가지는 오브젝트에서 끝난다. null은 더 이상의 프로토타입이 없다고 정의되며,프로토타입 체인의 종점 역할을 한다.
자바스크립트 객체는 속성을 저장하는 동적인 "가방"과 (자기만의 속성이라고 부른다) 프로토타입 객체에 대한 링크를 가진다. 객체의 어떤 속성에 접근하려할 때 그 객체 자체 속성 뿐만 아니라 객체의 프로토타입, 그 프로토타입의 프로토타입 등 프로토타입 체인의 종단에 이를 때까지 그 속성을 탐색한다.
아래 코드에는 어떤 속성에 접근 하려할 때 일어나는 상황이다.
// o라는 객체가 있고, 속성 'a' 와 'b'를 갖고 있다고 하자.
let f = function () {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a: 1, b: 2}
// f 함수의 prototype 속성 값들을 추가 하자.
f.prototype.b = 3;
f.prototype.c = 4;
// f.prototype = {b: 3, c: 4}; 라고 하지 마라, 해당 코드는 prototype chain 을 망가뜨린다.
// o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있다.
// o.[[Prototype]].[[Prototype]] 은 Object.prototype 이다.
// 마지막으로 o.[[Prototype]].[[Prototype]].[[Prototype]]은 null이다.
// null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없다.
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
console.log(o.a); // 1
// o는 'a'라는 속성을 가지는가? 그렇다. 속성의 값은 1이다.
console.log(o.b); // 2
// o는 'b'라는 속성을 가지는가? 그렇다. 속성의 값은 2이다.
// 프로토타입 역시 'b'라는 속성을 가지지만 이 값은 쓰이지 않는다. 이것을 "속성의 가려짐(property shadowing)" 이라고 부른다.
console.log(o.c); // 4
// o는 'c'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'c'라는 속성을 가지는가? 가지고 값은 4이다.
console.log(o.d); // undefined
// o는 'd'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'd'라는 속성을 가지는가? 아니다. 다시 프로토타입을 확인해보자.
// o.[[Prototype]].[[Prototype]]은 null이다. 찾는 것을 그만두자.
// 속성이 발견되지 않았기 때문에 undefined를 반환한다.
자바스크립트에 "메소드"라는건 없다. 하지만 자바스크립트는 객체의 속성으로 함수를 지정할 수 있고 속성 값을 사용하듯 쓸 수 있다. 속성 값으로 지정한 함수의 상속 역시 위에서 본 속성의 상속과 동일하다. (단 위에서 언급한 "속성의 가려짐" 대신 "메소드 오버라이딩, method overriding" 라는 용어를 사용한다)
사실상 자바스크립트의 상속은 셰도잉이라고 할 수 있음(한겹 더 가림의 느낌으로)
var o = {
a: 2,
m: function(b){
return this.a + 1;
}
};
console.log(o.m()); // 3
// o.m을 호출하면 'this' 는 o를 가리킨다.
var p = Object.create(o);
// p 는 프로토타입을 o로 가지는 오브젝트이다.
p.a = 12; // p 에 'a'라는 새로운 속성을 만들었다.
console.log(p.m()); // 13
// p.m이 호출 될 때 'this' 는 'p'를 가리킨다.
// 따라서 o의 함수 m을 상속 받으며,
// 'this.a'는 p.a를 나타내며 p의 개인 속성 'a'가 된다.
Object.create 이용
ECMAScript 5는 새로운 방법을 도입했다.Object.create라는 메소드를 호출하여 새로운 객체를 만들 수 있다. 생성된 객체의 프로토타입은 이 메소드의 첫 번째 인수로 지정된다.
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (상속됨)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined이다. 왜냐하면 d는 Object.prototype을 상속받지 않기 때문이다.
class키워드 이용
ECMAScript2015에는 몇 가지 키워드가 도입되어class를 구현하였다. 이런 생성 방식은 클래서 기반 언어의 개발자들에게 친숙하게 다가오나 동작 방식이 같지는 않다. 자바스크립트는 여전히 프로토타입 기반으로 남아있다. 새로 도입된 키워드는class,constructor,static,extends, 그리고super가 있다.
'use strict';
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
function Graph() {
this.vertexes = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertexes.push(v);
}
};
var g = new Graph();
// g 'vertexes' 와 'edges'를 속성으로 가지는 객체이다.
// 생성시 g.[[Prototype]]은 Graph.prototype의 값과 같은 값을 가진다.
성능
프로토타입 체인에 걸친 속성 검색으로 성능에 나쁜 영향을 줄 수 있으며, 때때로 치명적일 수 있다. 또한 존재하지도 않는 속성에 접근하려는 시도는 항상 모든 프로토타입 체인인 전체를 탐색해서 확인하게 만든다.
객체의 속성에 걸쳐 루프를 수행 하는 경우 프로토타입 체인 전체의모든열거자 속성에 대하여 적용된다. 객체 개인 속성인지 프로토타입 체인상 어딘가에 있는지 확인하기 위해서는 Object.prototype에서 모든 오브젝트로 상속된hasOwnProperty메소드를 이용할 필요가 있다. 다음 코드를 통하여 구체적인 예를 확인하여 보자.
function Animal(name, address) {
this.name = name;
this.address = address;
}
Animal.prototype.printName = function () {
console.log(`${this.name} ${this.address}`);
}
function Dog(name, address, owner) {
//아래줄은 클래스에서 super을 호출하는 것과 동일하다
//super(name, address);
Animal.call(this, name, address);
this.owner = owner;
}
//Dog가 Animal을 상속하기 위해
//Object.create(Animal.prototype) 인자로 전달한 프로토타입을 이용해서
//새로운 오브젝트를 만들어서 연결해줌
//원래 상태: Dog.prototype = Object.create(Object.prototype);
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.play = () => {
console.log('play with me!')
}
const dog1 = new Dog('멍멍이', '동대문구', '김하늘');
dog1.play();
dog1.printName();
연결형 상속(Concatenative inheritance)
연결형 상속은 한 객체의 속성을 다른 객체에 모두 복사함으로써 상속을 구현하는 방법이다.
이 상속법은 Javascript 객체의 동적 확장성을 이용한 방법이다. 객체 복사는 속성의 초기값을 저장하기 위한 좋은 방법이다: 이 방식은Object.assign()을 통해 구현하는 것이 보통이며 ES6 이전에 Lodash, Underscore, jQuery등의 라이브러리들이 .extend()와 비슷한 메소드로 제공한 방법이다.
const proto = {
hello: function hello() {
return `Hello, my name is ${ this.name }`;
}
};
const george = Object.assign({}, proto, {name: 'George'});
const msg = george.hello();
console.log(msg); // Hello, my name is George
위 포스팅에서 3가지의 함수 정의 방식을 알아보았다. 정의 방식은 달라도 결국 Function 생성자 함수를 통해 함수를 생성하는 것까지 확인하였다. 그런데 이 3가지 함수 정의 방식은 동작 방식에 약간의 차이가 있다.
var res = square(5);
function square(number) {
return number * number;
}
위 코드를 보면 함수 선언문으로 함수가 정의되기 이전에 함수 호출이 가능하다. 함수 선언문의 경우, 함수 선언의 위치와는 상관없이 코드 내 어느 곳에서든지 호출이 가능한데 이것을 함수 호이스팅(Function Hoisting)이라 한다.
자바스크립트는 ES6의 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅(Hoisting)한다.
호이스팅이란 var 선언문이나 function 선언문 등 모든 선언문이 해당Scope의 선두로 옮겨진 것처럼 동작하는 특성을 말한다. 즉, 자바스크립트는 모든 선언문(var, let, const, function,function*, class)이 선언되기 이전에 참조 가능하다.
함수 선언문으로 정의된 함수는 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object)에 저장한다. 즉,함수 선언, 초기화, 할당이 한번에 이루어진다.그렇기 때문에 함수 선언의 위치와는 상관없이 소스 내 어느 곳에서든지 호출이 가능하다.
다음은 함수 표현식으로 함수를 정의한 경우이다.
var res = square(5); // TypeError: square is not a function
var square = function(number) {
return number * number;
}
함수 선언문의 경우와는 달리 TypeError가 발생하였다.함수 표현식의 경우 함수 호이스팅이 아니라변수 호이스팅이 발생한다.
변수 호이스팅은 변수 생성 및 초기화와 할당이 분리되어 진행된다. 호이스팅된 변수는 undefined로 초기화 되고 실제값의 할당은 할당문에서 이루어진다.
함수 표현식은 함수 선언문과는 달리 스크립트 로딩 시점에 변수 객체(VO)에 함수를 할당하지 않고 runtime에 해석되고 실행되므로 이 두가지를 구분하는 것은 중요하다.
JavaScript: The Good Parts의 저자이며 자바스크립트의 권위자인 더글러스 크락포드(Douglas Crockford)는 이와 같은 문제 때문에 함수 표현식만을 사용할 것을 권고하고 있다. 함수 호이스팅이 함수 호출 전 반드시 함수를 선언하여야 한다는 규칙을 무시하므로 코드의 구조를 엉성하게 만들 수 있다고 지적한다.
또한 함수 선언문으로 함수를 정의하면 사용하기에 쉽지만 대규모 애플리케이션을 개발하는 경우 인터프리터가 너무 많은 코드를 변수 객체(VO)에 저장하므로 애플리케이션의 응답속도는 현저히 떨어질 수 있으므로 주의해야 할 필요가 있다.
// 함수 선언문
function square(number) {
return number * number;
}
// 함수 표현식
var square = function(number) {
return number * number;
};
함수 표현식 방식으로 정의한 함수는 함수명을 생략할 수 있다. 이러한 함수를익명 함수(anonymous function)이라 한다. 함수 표현식에서는 함수명을 생략하는 것이 일반적이다.
// 기명 함수 표현식(named function expression)
var foo = function multiply(a, b) {
return a * b;
};
// 익명 함수 표현식(anonymous function expression)
var bar = function(a, b) {
return a * b;
};
console.log(foo(10, 5)); // 50
console.log(multiply(10, 5)); // Uncaught ReferenceError: multiply is not defined
함수는 일급객체이기 때문에 변수에 할당할 수 있는데 이 변수는 함수명이 아니라 할당된 함수를 가리키는 참조값을 저장하게 된다. 함수 호출시 함수명이 아니라 함수를 가리키는 변수명을 사용하여야 한다.
var foo = function(a, b) {
return a * b;
};
var bar = foo;
console.log(foo(10, 10)); // 100
console.log(bar(10, 10)); // 100
변수 bar와 변수 foo는 동일한 익명 함수의 참조값을 갖는다.
함수 선언문으로 정의한 함수 square의 경우, 함수명으로 호출할 수 있었는데 이는 자바스크립트 엔진에 의해 아래와 같은 함수 표현식으로 형태가 변경되었기 때문이다.
var square = function square(number) {
return number * number;
};
함수명과 함수 참조값을 가진 변수명이 일치하므로 함수명으로 호출되는 듯 보이지만 사실은 변수명으로 호출된 것이다.
결국 함수 선언문도 함수 표현식과 동일하게 함수 리터럴 방식으로 정의되는 것이다.
Function 생성자 함수
함수 표현식으로 함수를 정의할 때 함수 리터럴 방식을 사용한다. 함수 선언문도 내부적으로 자바스크립트 엔진이 기명 함수 표현식으로 변환하므로 결국 함수 리터럴 방식을 사용한다.
따라서함수 선언문과 함수 표현식은 모두 함수 리터럴 방식으로 함수를 정의하는데 이것은 결국 내장 함수 Function 생성자 함수로 함수를 생성하는 것을 단순화시킨 short-hand(축약법)이다.
Function 생성자 함수는 Function.prototype.constructor 프로퍼티로 접근할 수 있다.
Function 생성자 함수로 함수를 생성하는 문법은 다음과 같다.
new Function(arg1, arg2, ... argN, functionBody)
var square = new Function('number', 'return number * number');
console.log(square(10)); // 100
프로그래밍 언어는 메모리 관리 방식에 따라 언매니지드 언어와 매니지드 언어로 분류할 수 있다.
C언어 같은 언매니지드 언어는 개발자가 명시적으로 메모리를 할당하고 해제하기 위해 malloc()와 free() 같은 저수준 메모리 제어 기능을 제공한다. 언매니지드 언어는 메모리 제어를 개발자가 주도할 수 있으므로 개발자의 역량에 따라 최적의 성능을 확보할 수 있지만 그 반대의 경우 치명적 오류를 생산할 가능성도 있다.
자바스크립트 같은 매니지드 언어는 메모리의 할당 및 해제를 위한 메모리 관리 기능을 언어 차원에서 담당하고 개발자의 직접적인 메모리 제어를 허용하지 않는다. 즉 개발자가 명시적으로 메모리를 할당하고 해제할 수 없다. 더 이상 사용하지 않는 메모리의 해제는 가비지 콜렉터가 수행하며, 이 또한 개발자가 관여할 수 없다. 매니지드 언어는 개발자의 역량에 의존하는 부분이 상대적으로 작아져 어느 정도 일정한 생산성을 확보할 수 있다는 장점이 있지만 성능 면에서는 어느 정도의 손실은 감수할 수밖에 없다.
가비지 콜렉터
가비지 콜렉터는 애플리케이션이 할당한 메모리 공간을 주기적으로 검사하여 더 이상 사용되지 않는 메모리를 해제하는 기능을 말한다. 더 이상 사용되지 않는 메모리란 간단히 말하자면 어떤 식별자도 참조하지 않는 메모리 공간을 의미한다. 자바스크립트는 가비지 콜렉터를 내장하고 있는 매니지드 언어로서 가비지 콜렉터를 통해 메모리 누수를 방지한다.
재할당 변수와 가비지 콜렉터
var이나 let 키워드를 사용해 선언한 변수에 값을 재할당하면 변수의 값은 이전 값에서 재할당한 값으로 변경된다.
이때, 이전 값이 저장되어 있던 메모리 공간을 지우고 그 메모리 공간에 재할당 값을 새롭게 저장하는 것이 아니라,
이전 값이 저장되어 있던 메모리 공간도 그대로 놔둔 채,
새로운 메모리 공간을 확보하고 그 메모리 공간에 재할당 값을 저장한다.
즉, 재할당된 var의 경우 초기화값인 undefined와 처음 할당값은 어떠한 변수도 값으로 갖고 있지 않는 상태가 될 것이다.
다시 말해 어떤 식별자와도 연결되어 있지 않은 값들이 발생한다.
이렇게 발생한 더 이상 필요하지 않는 값들은 아무도 사용하고 있지 않으니 해제하는 것이 바람직하다.
이렇게 불필요한 값들은 가비지 콜렉터에 의해 메모리에서 자동 해제된다.
단, 메모리에서 언제 해제될지는 예측할 수 없다.
undefined vs null
undefined는 개발자가 의도적으로 할당하기 위한 값이 아니라 자바스크립트 엔진이 변수를 초기화할 때 사용하는 값이다. 변수를 참조했을 때 undefined가 반환된다면 참조한 변수가 선언 이후 값이 할당된 적이 없는, 즉 초기화되지 않은 변수라는 것을 간파할 수 있다.
자바스크립트 엔진이 변수를 초기화하는데 사용하는 undefined를 개발자가 의도적으로 변수에 할당한다면 undefined의 본래 취지와 어긋날뿐더러 혼란을 줄 수 있으므로 권장하지 않는다.
변수에 값이 없다는 것을 명시하고 싶을 때는 undefined가 아니라 null을 할당한다.
undefined
undefined의 값은 undefined가 유일하다. var로 선언한 변수는 암묵적으로 undefined로 초기화된다. 다시 말해, 변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이뤄질 때까지 빈 상태(대부분 비어있지 않고 쓰레게 값이 들어있다)로 내버려 두지 않고, 자바스크립트 엔진이 undefined로 초기화한다. 따라서 변수를 선언한 이후 값을 할당하지 않은 변수를 참조하면 undefined가 반환된다.
null
null타입의 값은 null이 유일하다. 프로그래밍 언어에서 null은 변수에 값이 없다는 것을 의도적으로 명시(의도적 부재)할 때 사용한다. 변수에 null을 할당하는 것은 변수가 이전에 참조하던 값을 더 이상 참조하지 않겠다는 것이다. 이는 이전에 할당되어 있던 값에 대한 참조를 명시적으로 제거하는 것을 의미하며, 자바스크립트 엔진은 누구도 참조하지 않는 메모리 공간에 대해 가비지 콜렉션을 수행할 것이다.
js의 선언과 정의
다른 프로그래밍 언어에서는 선언과 정의를 엄격하게 구분해서 사용하는 경우가 있다.
예를 들어, C에서 선언과 정의는 "실제로 메모리 주소를 할당하는가"로 구분한다. 단순히 컴파일러에게 식별자의 존재만 알리는 것은 선언이고, 실제로 컴파일러가 변수를 생성해서 식별자와 메모리 주소가 연결되면 정의로 구분한다. 자바스크립트의 경우 변수를 선언하면 암묵적으로 정의가 이루어지기 때문에 선언과 정의의 구분이 모호하다.
ECMAScript 사양에서 변수는 '선언하다'라고 표현하고, 함수는 '정의하다'라고 표현한다.
3. 자바스크립트 엔진이 확보된 메모리 공간에 undefined라는 값을 암묵적으로 할당, 초기화
* undefined는 js에서 제공하는 원시타입의 값(primitive value)이다.
자바스크립트 엔진의 변수 선언
- 선언 단계 : 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알림
- 초기화 단계 : 값을 저장하기 위한 메모리 공간을 확보하고,
암묵적으로 undefined를 할당해 초기화
* 변수 이름을 비롯한 모든 식별자는 실행 컨텍스트(execution context)에 등록된다.
실행 컨텍스트(execution context)는 js 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 제공하고,
코드의 실행 결과를 실제로 관리하는 영역이다. 자바스크립트 엔진은 실행 컨텍스트를 통해 식별자와 스코프를 관리한다.
변수 이름과 변수 값은 실행 컨텍스트 내에 키/값 형식인 객체로 등록되어 관리된다.
선언하지 않은 식별자에 접근하면 참조에러(ReferenceError)발생, 식별자를 통해 값을 참조하려 했지만 자바스크립트 엔진에 등록된 식별자를 찾을 수 없을 때 발생하는 에러
console.log(score); // undefined
var score; // ① 변수 선언
score = 80; // ② 값의 할당
console.log(score); // 80
첫 줄에서 undefined이 출력되는 이유는 변수 선언이 소스코드가 한 줄씩 순차적으로 실행되는 시점, 런타임이 아니라 그 이전 단계에서 먼저 실행되기 때문이다.
자바스크립트 엔진은 소스 코드를 한줄씩 순차적으로 실행하기에 앞서 먼저 소스코드의 평가 과정을 거치면서 소스코드를 실행하기 위한 준비를 한다. 이때 소스 코드 실행을 위한 준비 단계인 소스코드의 평가 과정에서 자바스크립트 엔진은 변수 선언을 포함한 모든 선언문(변수 선언문, 함수 선언문 등)을 소스 코드에서 찾아내 먼저 실행한다. 그리고 소스 코드의 평가 과정이 끝나면 비로소 변수 선언을 포함한 모든 선언문을 제외하고 소스코드를 한줄씩 순차적으로 실행한다.
위의 예제도 변수 선언(선언 단계와 초기화 단계)이 소스코드가 순차적으로 실행되는 런타임 이전 단계에서 먼저 실행된다는 증거이다. 이처럼 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라한다.
사실 변수 선언뿐 아니라 var, let, const, function, function*, class 키워드를 사용해서 선언하는 모든 식별자(변수, 함수, 클래스 등)는 호이스팅된다. 모든 선언문은 런타임 이전 단계에서 먼저 실행되기 때문이다.
console.log(score); // undefined
score = 80; // 값의 할당
var score; // 변수 선언
console.log(score); // 80
주의할 점은 변수 선언고 값의 할당 실행 시점이 다르다는 것이다. 변수 선언은 소스코드가 순차적으로 실행되는 시점인 런타임 이전에 먼저 실행되지만 값의 할당은 소스코드가 순차적으로 실행된느 시점인 런타임에 실행된다.
ES2015는 스스로의this바인딩을 제공하지 않는화살표 함수를 추가했다(렉시컬 컨텍스트안의this값을 유지).
예시 1 : this의 값을 한 문맥에서 다른 문맥으로 넘기려면 다음 예시와 같이call()이나apply()를 사용
// call 또는 apply의 첫 번째 인자로 객체가 전달될 수 있으며 this가 그 객체에 묶임
var obj = {a: 'Custom'};
// 변수를 선언하고 변수에 프로퍼티로 전역 window를 할당
var a = 'Global';
//단 let a일 경우, undefined
function whatsThis() {
return this.a; // 함수 호출 방식에 따라 값이 달라짐
}
whatsThis(); // this는 'Global'. 함수 내에서 설정되지 않았으므로 global/window 객체로 초기값을 설정한다.
whatsThis.call(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
whatsThis.apply(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
예시 2
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 이어지는 인자들은 함수 호출에서 인수로 전달된다.
add.call(o, 5, 7); // 16
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 두 번째 인자는 함수 호출에서 인수로 사용될 멤버들이 위치한 배열이다.
add.apply(o, [10, 20]); // 34
화살표 함수에서 this는 자신을 감싼 정적 범위입니다. 전역 코드에서는 전역 객체를 가리킵니다.
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
참고: 화살표 함수를 call(), bind(), apply()를 사용해 호출할 때 this의 값을 정해주더라도 무시합니다.
// 객체로서 메서드 호출
var obj = {func: foo};
console.log(obj.func() === globalObject); // true
// call을 사용한 this 설정 시도
console.log(foo.call(obj) === globalObject); // true
// bind를 사용한 this 설정 시도
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
어떤 방법을 사용하든foo의this는 생성 시점의 것으로 설정됩니다(위 예시에서는 global 객체). 다른 함수 내에서 생성된 화살표 함수에도 동일하게 적용됩니다.this는 싸여진 렉시컬 컨텍스트의 것으로 유지됩니다.
// this를 반환하는 메소드 bar를 갖는 obj를 생성합니다.
// 반환된 함수는 화살표 함수로 생성되었으므로,
// this는 감싸진 함수의 this로 영구적으로 묶입니다.
// bar의 값은 호출에서 설정될 수 있으며, 이는 반환된 함수의 값을 설정하는 것입니다.
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
// obj의 메소드로써 bar를 호출하고, this를 obj로 설정
// 반환된 함수로의 참조를 fn에 할당
var fn = obj.bar();
// this 설정 없이 fn을 호출하면,
// 기본값으로 global 객체 또는 엄격 모드에서는 undefined
console.log(fn() === obj); // true
// 호출 없이 obj의 메소드를 참조한다면 주의하세요.
var fn2 = obj.bar;
// 화살표 함수의 this를 bar 메소드 내부에서 호출하면
// fn2의 this를 따르므로 window를 반환할것입니다.
console.log(fn2()() == window); // true
위 예시에서,obj.bar에 할당된 함수(함수bar라고 지칭)는 화살표 함수로 생성된 다른 함수(익명 함수 B)를 반환
결과로써 함수 B가 호출될 때 B의this는 영구적으로obj.bar(함수bar)의this로 설정됩니다.