ECMAScript 6 객체의 확장된 기능

객체의 확장된 기능

ECMAScript 6은 객체의 유용성을 향상 시키는데 중점을 두고 있습니다. 이것은 JavaScript의 거의 모든 값이 어떤 유형의 객체이기 때문에 의미가 있습니다. 또한 JavaScript 프로그램에 평균적으로 사용되는 객체의 수는 JavaScript 응용 프로그램의 복잡성이 증가함에 따라 계속 증가하고 있습니다. 이는 복잡한 프로그램이 항상 더 많은 객체를 생성한다는 것을 의미합니다. 객체가 많을수록 객체를 보다 효과적으로 사용할 필요성이 커집니다.

ECMAScript 6는 간단한 구문 확장부터 조작 및 상호 작용을 위한 옵션에 이르기까지 다양한 방법으로 객체의 사용성을 향상시킵니다.

객체 카테고리 (Object Category)

JavaScript는 브라우저나 Node.js와 같은 실행 환경에 의해 추가된 것과 다르게, 표준에 정의된 객체를 설명하기 위해 여러 용어를 사용하며, ECMAScript 6 사양은 이러한 객체의 각 카테고리에 대한 명확한 정의를 가지고 있습니다.

또한 언어 전체를 잘 이해하려면 이러한 용어를 이해하는 것이 매우 중요합니다. 객체의 카테고리는 다음과 같습니다.

  • 평범한 객체(Ordinary object)는 JavaScript의 객체에 대한 모든 기본 내부 동작을 가집니다.
  • 특수한 객체(Exotic object)는 어떤면에서 기본과 다른 내부 동작이 있습니다.
  • 표준 객체(Standard object)Array, Date등과 같이 ECMAScript 6에 의해 정의된 객체입니다. 표준 객체는 평범하거나 특이할 수 있습니다.
  • Built-in 객체는 스크립트가 실행되기 시작하면 JavaScript 실행 환경에 존재하게 됩니다. 모든 표준 객체는 Built-in 객체입니다.

ECMAScript 6에 정의된 다양한 객체를 설명하기 위해 위의 용어를 사용합니다.

객체 리터럴 문법 확장

객체 리터럴은 JavaScript에서 가장 인기있는 패턴 중 하나입니다. 구문에 따라 작성된 JSON은 인터넷의 거의 모든 JavaScript 파일에 있습니다. 객체 리터럴은 여러줄의 코드를 필요로하는 객체를 만드는 간결한 구문이기 때문에 매우 유용합니다. 다행히도 개발자를 위해 ECMAScript 6은 여러 가지 방법으로 구문을 확장하여 객체 리터럴을 더욱 강력하고 간결하게 만듭니다.

프로퍼티 초기화 단축 (Property Initializer Shorthand) 기능

ECMAScript 5 및 이전 버전에서 객체 리터럴은 단순히 name-value 쌍의 모음이었습니다. 즉, 값을 초기화할 때 중복이 있을 수 있습니다.

1
2
3
4
5
6
function createPerson(name, age) {
return {
name: name,
age: age
};
}

createPerson() 함수는 프로퍼티 이름이 함수 파라미터 이름과 같은 객체를 생성합니다. 하나는 객체 프로퍼티의 이름이고 다른 하나는 그 프로퍼티에 대한 값을 제공하지만 결과는 nameage의 중복으로 나타납니다. 리턴 객체의 키 name에는 파라미터 name 값이 할당되고 키 age에는 파라미터 age의 값이 할당됩니다.

ECMAScript 6에서는 프로퍼티 초기화의 단축(Property Initializer Shorthand)을 사용하여 프로퍼티 이름과 로컬 변수에 존재하는 중복을 제거할 수 있습니다. 객체 프로퍼티 이름이 로컬 변수 이름과 같으면 콜론과 값 없이 name을 포함할 수 있습니다. 예를 들어, createPerson()은 다음과 같이 ECMAScript 6으로 재작성할 수 있습니다.

1
2
3
4
5
6
function createPerson(name, age) {
return {
name,
age
};
}

객체 리터럴의 프로퍼티에 이름만 있으면 JavaScript 엔진은 Scope내의 같은 이름의 변수를 조사합니다. 변수를 찾으면 해당 변수의 값이 객체 리터럴의 동일한 이름에 지정됩니다. 위 예제에서, 객체 리터럴 프로퍼티 name에는 로컬 변수 name의 값이 할당됩니다.

이러한 확장 기능은 객체 리터럴 초기화를 훨씬 간결하게 만들고 명명 오류를 제거하는데 도움이됩니다. 로컬 변수와 같은 이름의 프로퍼티를 할당하는 것은 JavaScript에서 매우 빈번히 발견되는 패턴이므로 이런 확장기능은 환영할만 합니다.

간결한 메서드 (Concise Method)

또한 ECMAScript 6은 메서드를 객체 리터럴에 할당하는 구문을 향상시켰습니다. ECMAScript 5 및 이전 버전에서는 다음과 같이 이름을 먼저 지정하고 함수 정의를 지정하여 객체에 메서드를 추가해야합니다.

1
2
3
4
5
6
var person = {
name: "Nicholas",
sayName: function() {
console.log(this.name);
}
};

ECMAScript 6에서는 콜론 및 function 키워드를 제거하여 구문을 보다 간결하게 만듭니다. 이전 예제를 다음과 같이 다시 작성할 수 있습니다.

1
2
3
4
5
6
var person = {
name: "Nicholas",
sayName() {
console.log(this.name);
}
};

간결한 메서드 (Concise Method) 구문 이라고 하는 이 단축 구문은 앞의 예와 마찬가지로 person 객체에 대한 메서드를 만듭니다. sayName() 프로퍼티는 익명의 함수에 할당되며 ECMAScript 5 sayName() 함수와 동일한 특성을 가지고 있습니다. 한가지 차이점은 간결한 메서드 (Concise Method) 구문은 super(“super 참조를 사용한 쉬운 프로토 타입 액세스”섹션에서 나중에 설명합니다.)를 사용하는 반면 간결하지 않는 방법은 사용할 수 없다는 점입니다.

간결한 메서드 (Concise Method) 구문을 사용하여 생성된 메서드의 name 프로퍼티는 괄호 앞에 사용된 이름입니다. 마지막 예제에서 person.sayName()name 프로퍼티는 “sayName”입니다.

프로퍼티의 계산된 이름

ECMAScript 5 및 이전 버전에서는 프로퍼티를 점 표기 대신 대괄호로 설정하면 객체 인스턴스의 프로퍼티 이름을 계산하여 지정할 수 있었습니다. 대괄호를 사용하여 변수의 식별자를 사용하면, 구문 오류가 발생할 수 있는 문자가 포함된 문자열 및 문자열 리터럴을 사용하여 프로퍼티 이름을 지정할 수 있습니다. 아래 예제를 살펴보겠습니다.

1
2
3
4
5
6
7
8
var person = {},
lastName = "last name";
person["first name"] = "Nicholas";
person[lastName] = "Zakas";
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

lastName"last name" 값이 할당되어 이 예제의 두 프로퍼티 이름 모두 공백을 사용합니다. 그러므로 점 표기법을 사용하여 프로퍼티 이름을 참조할 수 없습니다. 그러나 대괄호 표기법을 사용하면 모든 문자열 값을 프로퍼티 이름으로 사용할 수 있으므로 "first name""Nicholas"에 할당하고, "last name"을 “Zakas”에 할당하면 정상 실행됩니다.

또한 아래와 같이 문자열 리터럴을 객체 리터럴의 프로퍼티 이름으로 직접 사용할 수 있습니다.

1
2
3
4
5
var person = {
"first name": "Nicholas"
};
console.log(person["first name"]); // "Nicholas"

이 패턴은 미리 알려진 프로퍼티 이름에 적용되며 문자열 리터럴로 나타낼 수 있습니다. 그러나 프로퍼티 이름 "first name"이 변수에 포함되어 있거나 (앞의 예에서와 같이) 계산되어야 하는 경우 ECMAScript 5에서 객체 리터럴을 사용하여 해당 프로퍼티를 정의할 수 있는 방법이 없습니다.

ECMAScript 6에서 계산된 프러퍼티 이름은 객체 리터럴 구문의 일부이며 객체 인스턴스에서 계산된 프로퍼티 이름을 참조하는데 사용된 동일한 대괄호 표기법을 사용합니다.

1
2
3
4
5
6
7
8
9
var lastName = "last name";
var person = {
"first name": "Nicholas",
[lastName]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

객체 리터럴 안의 대괄호는 속성 이름이 계산됨을 나타내므로 내용이 문자열로 평가됩니다. 즉, 다음과 같이 표현식을 포함할 수도 있습니다.

1
2
3
4
5
6
7
8
9
var suffix = " name";
var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"

이러한 속성은 “first name”과 “last name”으로 평가되며 나중에 이 문자열을 사용하여 프로퍼티를 참조할 수 있습니다. 객체 인스턴스에 대괄호 표기법을 사용하면, 객체 리터럴 내에서 계산된 프로퍼티 이름으로 적용되기 때문에 어떤것 이든 대괄호 안에 넣을 수 있습니다.

새로운 메서드들

ECMAScript가 ECMAScript 5를 시작할때 디자인한 목표 중 하나는 Object.prototype에 새로운 전역 함수 또는 메서드를 만드는 것을 피하고 대신 새 메서드를 사용할 수 있는 객체를 찾으려고 했습니다. 결과적으로, Global Object는 다른 오브젝트가 더 적합하지 않을 때 점점 많은 메서드가 추가되었습니다. ECMAScript 6에서는 특정 작업을 보다 쉽게하기 위해 디자인된 Global Object에 몇 가지 새로운 메서드를 도입했습니다.

Object.is() 메서드

JavaScript에서 두 값을 비교하고자 할 때, ==(equals 연산자) 또는 ===(Identically equals 연산자)를 사용하는 데 익숙합니다. 많은 개발자들이 비교하는 동안 타입 강제(Type coercion)를 피하기 위해 후자를 선호합니다. 그러나 === 연산자조차도 완전히 정확하지는 않습니다. 예를 들어, +0과 -0 값은 JavaScript 엔진에서 다르게 표현 되더라도 ===는 같은 값으로 간주됩니다. 또한 NaN === NaNfalse를 반환합니다. NaN 프로퍼티를 적절히 감지하기 위해서는 isNaN()을 사용해야합니다.

ECMAScript 6는 Identically equals 연산자의 단점을 보충하기 위해 Object.is() 메서드를 도입했습니다. 이 메서드는 두 개의 파라미터를 받아들여 값이 동일하면 true를 리턴합니다. 두 값은 동일한 타입이고 동일한 값을 가질 때 동등한 것으로 간주됩니다. 여기 몇 가지 예제를 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(5 == 5); // true
console.log(5 == "5"); // true
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false

대부분의 경우 Object.is()=== 연산자와 똑같이 작동합니다. 유일한 차이점은 +0-0이 동등하지 않은 것으로 간주되고 NaNNaN과 같은 것으로 간주된다는 것입니다. 그렇기 때문에 Equal 연산자 사용을 중지할 필요는 없습니다. 위와 같이 특수한 경우에 == 또는 === 대신 Object.is() 사용을 선택할 수도 있습니다.

Object.assign() 메서드

Mixin은 JavaScript에서 객체 구성을 위한 가장 인기있는 패턴 중 하나입니다. Mixin에서 한 객체는 다른 객체에서 프로퍼티와 메서드를 받습니다. 많은 JavaScript 라이브러리에는 다음과 유사한 Mixin 메서드가 있습니다.

1
2
3
4
5
6
7
function mixin(receiver, supplier) {
Object.keys(supplier).forEach(function(key) {
receiver[key] = supplier[key];
});
return receiver;
}

mixin() 함수는 supplier 자신의 프로퍼티를 반복하여 receiver에 복사합니다 (얕은 복사, 프로퍼티 값이 객체 일 때 객체 참조가 공유됩니다). 이렇게하면 아래 코드와 같이 receiver가 상속 없이 새 프로퍼티를 얻을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
function EventTarget() { /*...*/ }
EventTarget.prototype = {
constructor: EventTarget,
emit: function() { /*...*/ },
on: function() { /*...*/ }
};
var myObject = {};
mixin(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

위 코드에서 myObjectEventTarget.prototype 객체로부터 메서드를 받습니다. 이것은 myObject에게 이벤트를 퍼블리시하고 emit()on()메서드를 사용하여 이벤트를 구독하는 기능을 제공합니다.

이 패턴은 ECMAScript 6가 Object.assign() 메서드를 추가할 만큼 충분히 대중적이되어, 같은 방식으로 동작하고, Receiver 와 임의의 Supplier를 받아 들인 다음 Receiver를 반환합니다. mixin()에서 assign()으로 이름을 바꾸면 실제 작업이 반영됩니다. mixin()함수는 대입 연산자(=)를 사용하기 때문에 접근자(accessor) 프로퍼티를 접근자(accessor) 프로퍼티로 Receiver에 복사할 수 없습니다. 이러한 차이를 반영하기 위해 Object.assign()이라는 이름이 선택되었습니다.

다양한 라이브러리에서 동일한 기능을 수행하는 비슷한 메서드가 있을 수 있으며 대부분 extend()mix()을 사용합니다. ECMAScript 6에는 Object.assign() 메서드 외에도 Object.mixin() 메서드가 있습니다. 가장 큰 차이점은 Object.mixin()도 접근자 프로퍼티를 이용해 복사되었지만 super (이 장의 “Super 참조를 이용한 쉬운 프로토 타입 액세스”절에서 설명 함) 사용에 대한 우려로 이 메서드가 제거 되었습니다.

mixin() 함수가 사용된 곳이면 어디에서나 Object.assign()을 사용할 수 있습니다. 다음 코드는 그러한 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
function EventTarget() { /*...*/ }
EventTarget.prototype = {
constructor: EventTarget,
emit: function() { /*...*/ },
on: function() { /*...*/ }
}
var myObject = {}
Object.assign(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

Object.assign() 메서드는 여러 Supplier를 받아들이며 ReceiverSupplier가 지정된 순서대로 프로퍼티를 수신합니다. 이는 두 번째 SupplierReceiver의 첫 번째 Supplier의 값을 덮어 쓸 수 있음을 의미합니다. 다음 코드를 살펴 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var receiver = {};
Object.assign(receiver,
{
type: "js",
name: "file.js"
},
{
type: "css"
}
);
console.log(receiver.type); // "css"
console.log(receiver.name); // "file.js"

receiver.type의 값은 “css”입니다. 왜냐하면 두 번째 Supplier가 첫 번째 Supplier의 값을 덮어 쓰기 때문입니다.

Object.assign() 메서드는 ECMAScript 6에 큰 변경사항은 아니지만 많은 JavaScript 라이브러리에서 볼 수있는 공통 기능을 공식화 한것입니다.

접근자(Accessor) 프로퍼티로 작업하기

Object.assign()은 Supplier가 접근자(Accessor) 프로퍼티를 가질 때 Receiver에 접근자(Accessor) 프로퍼티를 생성하지 않는다는 것을 명심하십시오. Object.assign()은 대입 연산자를 사용하기 때문에 Supplier의 접근자(Accessor) 프로퍼티는 Receiver의 데이터 프로퍼티가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var receiver = {},
supplier = {
get name() {
return "file.js"
}
};
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value); // "file.js"
console.log(descriptor.get); // undefined

이 코드에서 Supplier는 name이라는 접근자(Accessor) 프로퍼티를 가지고 있습니다.

Object.assign() 메서드를 사용한 후에 receiver.name의 값은 "file.js"인 데이터 프로퍼티로서 존재합니다. 왜냐하면 Object.assign()을 호출 했기 때문에 supplier.name"file.js"를 리턴했기 때문입니다.

객체 리터럴의 중복 프로퍼티 처리

ECMAScript 5 strict 모드는 중복이 발견되면 오류를 던질 중복 객체 프로퍼티에 대한 검사를 도입했습니다. 예를 들어, 이 코드는 문제가 있었습니다.

1
2
3
4
5
6
"use strict";
var person = {
name: "Nicholas",
name: "Greg" // ES5 strict mode에서는 구문오류가 발생합니다.
};

ECMAScript 5의 strict 모드에서 실행될 때, 두 번째 name 프로퍼티는 구문 오류를 일으킵니다. 그러나 ECMAScript 6에서는 중복 프로퍼티 검사가 제거되었습니다. strictnonstrict 모드 코드는 더 이상 중복 프로퍼티를 검사하지 않습니다. 대신, 아래 코드에서 보여주는 대로 같은 이름의 마지막 프로퍼티가 프로퍼티의 실제값이 됩니다.

1
2
3
4
5
6
7
8
"use strict";
var person = {
name: "Nicholas",
name: "Greg" // ES6 strict mode에서 에러가 발생하지 않습니다.
};
console.log(person.name); // "Greg"

위 예제에서 person.name의 값은 그 프로퍼티에 할당된 마지막 값이기 때문에 “Greg”입니다.

자신의 프로퍼티 열거 순서

ECMAScript 5는 객체 프로퍼티의 열거순서를 정의하지 않았습니다. JavaScript 엔진 공급 업체에 맡겨 놓았기 때문입니다. 그러나 ECMAScript 6에서는 열거될 경우 자신의 프로퍼티를 반환해야하는 순서를 엄격하게 정의합니다. 이것은 Object.getOwnPropertyNames()Reflect.ownKeys (12 장에서 다룹니다.)를 사용하여 프로퍼티를 반환하는 방법에 영향을 미칩니다. 그리고 Object.assign()에 의해 프로퍼티가 처리되는 순서에도 영향을 미칩니다.

자신의 프로퍼티 기본 열거 순서는 다음과 같습니다.

  1. 모든 숫자키는 오름차순으로 표시됩니다.
  2. 모든 문자열키는 객체에 추가된 순서대로 표시됩니다.
  3. 모든 Symbol(6 장에서 다룹니다.)키는 객체에 추가된 순서대로 표시됩니다.

아래 예제를 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
a: 1,
0: 1,
c: 1,
2: 1,
b: 1,
1: 1
};
obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"

Object.getOwnPropertyNames() 메서드는 obj의 프로퍼티를 0, 1, 2, a, c, b, d 순서로 리턴합니다. 숫자 키는 객체 리터럴에서 순서가 어긋나지만 함께 그룹화되고 정렬됩니다. 문자열 키는 숫자 키 뒤에 오며 obj에 추가된 순서대로 나타납니다. 객체 리터럴 자신의 키가 먼저오고 나중에 추가된 동적 키 (이 경우 d)가 옵니다.

for-in loop는 여전히 모든 JavaScript 엔진이 같은 방식으로 구현하지 않았기 때문에 불특정한 열거 순서를 가지고 있습니다. Object.keys() 메서드와 JSON.stringify()는 모두 for-in과 같은 (불특정한) 열거 순서를 사용하도록 지정되어 있습니다.

열거 순서는 JavaScript가 작동하는 방식에 미묘한 변화이지만, 정확한 열거에 의존하는 프로그램을 찾는건 드문일이 아닙니다. 열거 순서를 정의함으로써 ECMAScript 6은 열거형에 의존하는 JavaScript 코드가 어디에 실행되는지에 관계없이 올바르게 작동하도록합니다.

더 강력한 프로퍼티들

Prototype은 JavaScript의 상속의 토대이며 ECMAScript 6는 Prototype을 계속해서 더 강력하게 만듭니다. 초기 버전의 JavaScript는 Prototype을 통해 수행할 수있는 작업을 심각하게 제한했습니다. 그러나 언어가 발전하고 개발자가 Prototype이 어떻게 작동하는지 더 잘 알게됨에 따라 개발자들은 Prototype을 더 많이 컨트롤 하고, 쉬운 방법으로 제어하기를 원했습니다. 결과적으로 ECMAScript 6는 Prototype을 약간 개선했습니다.

Object의 Prototype 변경

일반적으로 객체의 Prototype은 객체가 생성될 때 생성자 또는 Object.create()메서드를 통해 지정됩니다. 인스턴스 생성후 객체의 Prototype이 변경되지 않는다는 생각은 ECMAScript 5까지 JavaScript 프로그래밍에서 가장 큰 가정 중 하나였습니다. ECMAScript 5는 주어진 객체의 Prototype을 찾기하기 위해 Object.getPrototypeOf() 메서드를 추가했지만 인스턴스화 후에 객체의 Prototype을 변경하는 표준 방법이 여전히 부족했습니다.

ECMAScript 6는 Object.setPrototypeOf() 메서드를 추가함으로써 이러한 가정을 바꿉니다. 이 메서드는 주어진 객체의 Prototype을 변경할 수 있게합니다. Object.setPrototypeOf() 메서드는 두 개의 파라미터를 받는데, 첫 번째는 Prototype이 변경 되어야하는 객체, 그리고 두 번째는 첫 번째 파라미터의 Prototype이 되어야하는 객체입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
// prototype은 person
let friend = Object.create(person);
console.log(friend.getGreeting()); // "Hello"
console.log(Object.getPrototypeOf(friend) === person); // true
// prototype을 dog로 설정
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof"
console.log(Object.getPrototypeOf(friend) === dog); // true

위 코드는 persondog라는 두 개의 기본 객체를 정의합니다. 두객체 모두 문자열을 반환하는 getGreeting() 메서드를 가지고 있습니다. 객체 friend는 먼저 person 객체를 상속받았기 때문에 getGreeting()"Hello"를 출력합니다. 그리고 Prototypedog 객체가 될 때 person.getGreeting()은 원래의 person과 관계가 깨졌기 때문에 "Woof"를 출력합니다.

객체의 Prototype의 실제 값은[[Prototype]]이라는 내부 전용 프로퍼티에 저장됩니다. Object.getPrototypeOf() 메서드는 [[Prototype]]에 저장된 값을 리턴하고 Object.setPrototypeOf()[[Prototype]]에 저장된 값을 변경합니다. 그러나 이 메서드들이 [[Prototype]]의 값을 이용해 작업하는 유일한 방법은 아닙니다.

Super 참조를 이용한 쉬운 Prototype 접근

앞서 언급했듯이, Prototype은 JavaScript에 매우 중요하며 많은 작업이 ECMAScript 6에서 사용하기가 더 쉬워졌습니다. 또 다른 개선점은 객체의 Prototype에 대한 기능을 보다 쉽게 액세스할 수 있게 해주는 super 참조의 도입입니다. 예를 들어, 객체 인스턴스의 메서드를 오버라이드하여 동일한 이름의 Prototype 메서드도 호출하도록 하는 다음 예제를 참고하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
}
};
// prototype을 person으로 설정
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(Object.getPrototypeOf(friend) === person); // true
// prototype을 dog로 설정
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof, hi!"
console.log(Object.getPrototypeOf(friend) === dog); // true

위의 예제에서, friend에 대한 getGreeting()은 같은 이름의 Prototype 메서드를 호출합니다. Object.getPrototypeOf() 메서드는 올바른 Prototype이 호출되었는지 확인한 다음 추가 문자열이 출력에 더해집니다. 게다가 .call(this)Prototype 메서드 내의 this 값이 올바르게 설정되도록합니다.

Prototype에서 메서드를 호출하기 위해 Object.getPrototypeOf().call(this)를 사용하는 것은 다소 복잡하기 때문에 ECMAScript 6는 super를 도입했습니다. 간단히 말해,super는 현재 객체의 Prototype을 가리키는 포인터이며, 사실상Object.getPrototypeOf(this) 값입니다. 다음과 같이 getGreeting() 메서드를 단순화할 수있습니다.

1
2
3
4
5
6
7
let friend = {
getGreeting() {
// 이전 예제에서 이것은 다음과 같습니다.
// Object.getPrototypeOf(this).getGreeting.call(this)
return super.getGreeting() + ", hi!";
}
};

위 예제에서 super.getGreeting()에 대한 호출은 Object.getPrototypeOf(this).getGreeting.call(this)와 동일합니다. 비슷하게, 간결한 메서드 내부에 있는 경우 super 참조를 사용하여 객체 Prototype의 모든 메서드를 호출할 수 있습니다. 하지만 간결한 메서드 밖에서 super를 사용하려고 시도하면 다음과 같이 구문 오류가 발생합니다.

1
2
3
4
5
6
let friend = {
getGreeting: function() {
// syntax error
return super.getGreeting() + ", hi!";
}
};

이 예제는 함수와 함께 명명된 프로퍼티를 사용하고 super가 이 컨텍스트에서 유효하지 않기 때문에 super.getGreeting()을 호출하면 구문 오류가 발생합니다.

super 레퍼런스는 여러 레벨의 상속이있을 때 정말 강력합니다. 왜냐하면 Object.getPrototypeOf()가 더 이상 모든 상황에서 작동하지 않기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
getGreeting() {
return "Hello";
}
};
// prototype is person
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
// prototype is friend
let relative = Object.create(friend);
console.log(person.getGreeting()); // "Hello"
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(relative.getGreeting()); // error!

relative.getGreeting()이 호출되어 Object.getPrototypeOf()를 호출하면 오류가 발생합니다. 그것은 thisrelative이고, relativePrototypefriend 객체이기 때문입니다. friend.getGreeting().call()relativethis로 호출하면 프로세스는 다시 시작되어 스택 오버 플로우 오류가 발생할 때까지 재귀적으로 계속 호출됩니다.

이 문제를 ECMAScript 5에서는 해결하기가 어렵지만 ECMAScript 6의 super를 이용하면 쉽게 해결할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
getGreeting() {
return "Hello";
}
};
// prototype is person
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
// prototype is friend
let relative = Object.create(friend);
console.log(person.getGreeting()); // "Hello"
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(relative.getGreeting()); // "Hello, hi!"

super 참조는 동적이 아니기 때문에 항상 올바른 객체를 참조합니다. 이 경우, super.getGreeting()은 얼마나 많은 다른 객체가 그 메서드를 상속 받았는지에 관계없이 항상 person.getGreeting ()을 참조합니다.

Method의 공식적인 정의

ECMAScript 6 이전에는 "Method"의 개념이 공식적으로 정의되지 않았습니다. Method는 데이터 대신 함수가 포함된 객체 프로퍼티었습니다. ECMAScript 6는 Method를 객체 내부의 [[HomeObject]] 프라퍼티를 가진 함수로 공식적으로 정의합니다. 다음 내용을 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
let person = {
// method
getGreeting() {
return "Hello";
}
};
// not a method
function shareGreeting() {
return "Hi!";
}

위 예제는 getGreeting()이라는 단일 메서드로 person을 정의합니다. getGreeting()[[HomeObject]]는 함수를 객체에 직접 할당함으로써 person을 정의합니다. 반면에 shareGreeting() 함수는 생성될 때 객체에 할당되지 않았으므로 [[HomeObject]]가 지정되어 있지 않습니다. 대부분의 경우 이 차이는 중요하지 않지만 super 참조를 사용할 때 매우 중요합니다.

super에 대한 참조는 무엇을 할 것인가를 결정하기 위해 [[HomeObject]]를 사용합니다. 첫 번째 단계는 [[HomeObject]]에서 Object.getPrototypeOf()를 호출하여 Prototype에 대한 참조를 가져 오는 것입니다. 그런 다음 Prototype의 이름이 같은 함수가 검색됩니다. 마지막으로, 바인딩이 설정되고 Method가 호출됩니다. 다음은 예제를 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
getGreeting() {
return "Hello";
}
};
// prototype is person
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting()); // "Hello, hi!"

friend.getGreeting() 호출은 person.getGreeting()의 값을 ", hi!"와 결합된 문자열을 반환합니다. friend.getGreeting()
[[HomeObject]]friend이고 friendPrototypeperson이므로 super.getGreeting()person.getGreeting.call(this)와 동일합니다.

요약

객체는 JavaScript 프로그래밍의 중심이며, ECMAScript 6는 객체를 보다 쉽게 다루고 더 강력하게 만드는 유용한 변경 사항을 만들었습니다.

ECMAScript 6은 객체 리터럴을 몇 가지 변경했습니다. 간략한 프로퍼티 정의는 Scope 내 변수와 동일한 이름을 가진 프로퍼티를 쉽게 지정합니다. 계산된 프로퍼티 이름을 사용하면 리터럴 이외의 값을 프로퍼티 이름으로 지정할 수 있습니다. 간략한 메서드는 콜론과 function 키워드를 완전히 생략함으로써 훨씬 적은 수의 문자를 입력하여 객체 리터럴에 대한 메서드를 정의할 수 있게합니다. ECMAScript 6는 객체의 중복 프로퍼티 이름에 대해서도 strict 모드 검사를 느슨하게합니다. 즉, 객체 리터럴에 오류가 발생하지 않고 같은 이름의 두 프로퍼티를 가질 수 있습니다.

Object.assign() 메서드는 한번에 하나의 객체에 대해 여러 속성을 변경하는 것을 더 쉽게 만듭니다. mixin 패턴을 사용하면 매우 유용할 수 있습니다. Object.is() 메서드는 어떤 값에 대해서도 엄격한 equal을 수행하며 특별한 JavaScript 값을 다룰 때 효과적인
===의 안전한 버전입니다.

ECMAScript 6에서는 자체 프로퍼티에 대한 열거 순서가 명확하게 정의되었습니다. 프로퍼티를 열거하면 숫자 키가 항상 오름차순으로 먼저 나오고 문자열 키가 삽입 순서에 맞춰 나오고 Symbol 키가 삽입 순서로 옵니다.

ECMAScript 6의Object.setPrototypeOf()메서드 덕택에 객체의 Prototype을 이미 생성 한 후에 수정할 수 있습니다.

마지막으로,super 키워드를 사용하여 객체의 Prototype에 대한 메서드를 호출할 수 있습니다. super를 사용하여 호출된 메서드 내부의 this 바인딩은 this의 현재 값으로 자동으로 작동하도록 설정됩니다.


이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://leanpub.com/understandinges6/read#leanpub-auto-expanded-object-functionality]

참고

공유하기