계산된 속성 이름 (computed property name)
계산된 속성 이름 (computed property name)
은 표현식의 결과를 프로퍼티의 이름으로 쓸 수 있게 해준다.
예제1
const name = "answer";
const obj = {
[name]: 100,
};
console.log(obj);
예제2: 작동 순서 알아보기
let counter = 0;
const obj = {
[counter++]: counter++,
[counter++]: counter++,
};
console.log(obj); // {"0": 1, "2": 3}
단축 속성 (shorthand property)
단축 속성 (shorthand property)
은 프로퍼티의 키와 값이 동일할 때 아래와 같이 표현할 수 있도록 해준다.
예제
function getMinMax(nums) {
const min, max;
return {min, max};
}
객체 프로토타입 얻기 및 설정하기
Object.getPrototypeOf(o)
를 통해 객체의 프로토타입을 얻을 수 있다.Object.setPrototypeOf(o)
를 통해 객체의 프로토타입을 설정할 수 있다.
기존의
__proto__
는 deprecated 되었으므로, 더이상 사용하지 않는 것이 좋다.
객체를 만든 후 프로토타입을 변경하면, 객체를 최적화 해제하여 속성 조회가 훨씬 느려질 수 있으니 주의해야 한다.
메서드 문법과 super
외부 클래스
메서드 문법은 더 간결하며, super
에 접근도 가능하다.
예제1: 메서드 문법을 사용하지 않음
const obj1 = {
name: "Joe",
say: function () {
console.log(this.name);
},
};
obj1.say(); // "Joe"
say()
메서드를 호출했다.[[HomeObject]]
는null
을 가리키고 있다.
예제2: 메서드 문법을 사용함
const obj1 = {
name: "Joe",
say() {
console.log(this.name);
},
};
obj1.say(); // "Joe"
say()
메서드를 호출했다.[[HomeObject]]
가Object
를 가리키고 있어서,super
키워드를 통해 상위 메서드를 불러올 수 있다.
예제3: 메서드 문법을 사용하지 않은 채로 상위 클래스의 메서드 이용하기
const obj = {
toString: function () {
return Object.prototype.toString.call(this).toUpperCase();
},
};
console.log(obj.toString()); // "[OBJECT OBJECT]"
- 프로토타입에 직접 접근하여
this
를 바인딩했다.
예제4: 메서드 문법을 사용하여 상위 클래스의 메서드 이용하기
const obj = {
toString() {
return super.toString().toUpperCase();
},
};
console.log(obj.toSring()); // "[OBJECT OBJECT]"
super
를 이용하면,[[HomeObject]]
에 접근할 수 있다.- 메서드의 이름 역시
computed property
로 표현될 수 있다.- ex)
[variableA + variableB]() { ... }
- ex)
예제5: 원리 살펴보기
const obj = {
toString() {
return super.toString().toUpperCase();
},
};
Object.setPrototypeOf(obj, {
toString() {
return "a different string";
},
});
console.log(obj.toString()); // "A DIFFERENT STRING"
- 프로토타입이 실제로
[[HomeObject]]
가 들어오는 곳이다. super
는 코드가 실행될 때, 현재 프로토타입을 가져온다.- 프로토타입 객체의 속성을 조회하고 사용한다.
자바스크립트 엔진은
super
키워드를 사용하지 않는다면, 링크를 최적화할 가능성이 높다.
심볼
- 자바스크립트 프로퍼티의 키로 사용된다.
- 키는 문자열 혹은 심볼이다.
- 심볼은 고유한 키가 된다. 유일하다는 것이 주요 목적과 특징이다.
심볼이 탄생하게된 배경
- 내장 오브젝트를
toString()
메서드를 이용해 출력하면,[object TypeName]
과 같이 출력된다.- ex)
[object Array]
,[object, Object]
,[object XYZ]
- ex)
toString()
의 출력 내용을[object CustomType]
으로 바꾸고 싶다면?toString()
메서드만 새로 오버라이드 해도 되겠지만, 여러 군데에TypeName
이 쓰였다면 난감한 일이다.- 객체 자체의 이름을 바꿔
TypeName
을 바꾼다면, 해당 객체를 사용하던 모든 코드가 망가진다.
심볼은 위의 문제를 해결한다.
class Example1 {}
class Example2 {
get [Symbol.toStringTag]() {
return "Example2";
}
}
console.log(new Example1().toString()); // [object Object]
console.log(new Example2().toString()); // [object Example2]
[Symbol.toStringTag]()
의 내용을 바꿈으로써, 해당 패턴이 포함된 모든 메서드의 동작을 일괄적으로 변경할 수 있다.
심볼 생성법 1: Symbol()
함수 이용하기
const symbol = Symbol("description");
- 매번 다른
Symbol
이 생성된다. Symbol()
함수에 넘긴 인자는 디버깅을 위한 설명일뿐, 설명이 같아도Symbol
은 동등하지 않다.
심볼 생성법 2: 전역 심볼 사용하기
const BarkCounter = (() => {
const barks = Symbol("nifty-barks");
return class BarkCounter {
constructor(other = null) {
this[barks] = other && barks in other ? other[barks] : 0;
}
bark() {
return ++this[barks];
}
showBarks(label) {
console.log(label + ": Barks = " + this[barks]);
}
};
})();
const b1 = new BarkCounter();
b1.bark(); // 1
b1.bark(); // 2
b1.showBarks("b1"); // b1: Barks = 2
const b2 = new BarkCounter(b1);
b2.showBarks("b2"); // b2: Barks = 2
- 생성자에서
barks
프로퍼티가 있는 객체를 받으면, 해당 객체의barks
프로퍼티를this[barks]
에 할당하여, 숫자가 유지된다. - IIFE 를 이용한 스코프 가두기를 통해 단 하나의
"nifty-barks"
심볼만 생성하였으므로, 숫자를 이어받는 것이 가능하다. - 위의 코드를 내부
iframe
과 같이 별개의 영역에서 실행한다면, 독립된Symbol
이 생성된다.
barkcounter-version-1.js
const BarkCounter = (() => {
const barks = Symbol("nifty-barks");
return class BarkCounter {
constructor(other = null) {
this[barks] = other && barks in other ? other[barks] : 0;
}
bark() {
return ++this[barks];
}
showBarks(label) {
console.log(label + ": Barks = " + this[barks]);
}
};
})();
1-barkcounter-main-1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BarkCounter Across Realms - Main - Version 1</title>
</head>
<body>
<script src="barkcounter-version-1.js"></script>
<script>
var barkCounterFrame = document.createElement("iframe");
barkCounterFrame.addEventListener("load", function () {
const b1 = new BarkCounter();
b1.bark();
b1.bark();
b1.showBarks("main"); // main: Barks = 2
barkCounterFrame.contentWindow.useBarkCounter(b1);
});
barkCounterFrame.src = "1-barkcounter-frame-1.html";
document.body.appendChild(barkCounterFrame);
</script>
</body>
</html>
1-barkcounter-frame-1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BarkCounter Across Realms - Frame - Version 1</title>
</head>
<body>
<script src="barkcounter-version-1.js"></script>
<script>
function useBarkCounter(b1) {
b1.showBarks("frame-b1");
const b2 = new BarkCounter(b1);
b2.showBarks("frame-b2");
}
</script>
</body>
</html>
- 위의 코드를 작성하고, 웹서버를 켠 뒤
1-barkcounter-main-1.html
에 접근하면,frame-b2: Barks = 0
이 되는 것을 볼 수 있다. - iframe 에서 독립적인
BarkCounter
스코프를 또 만들어서 그렇다. - 심볼을 생성하는 부분을
Symbol.for("nifty-barks")
로 코드를 약간 바꾸면, 전역 레지스트리에서 고유한 키를 가진 심볼을 가져올 수 있는데, 이를 이용하면frame-b2: Barks = 2
가 되는 것을 볼 수 있다.
심볼은 은닉을 위한 것이 아니다.
Object.getOwnPropertySymbols()
메서드를 통해 해당 오브젝트가 사용하는 심볼을 가져올 수 있다.- 비공개 속성은
WeakMap
을 통해 사용할 수 있다.
초기의 심볼은 "프라이빗 이름 객체" 로 시작했는데, 시간이 지남에 따라 단순히 고유 식별자를 갖는 용도로 쓰이게 됐다.
잘 알려진 심볼
- MDN Symbols 페이지에 가면,
Properties
에 잘 알려진 심볼의 목록들이 있다. - 잘 알려진 심볼들은 전역으로 취급되며, 특정한 목적을 가지고 만들어져있다.
새로운 객체 함수
- ES2015, ES2017 업데이트에서
Object
에 몇가지 새로운 기능이 추가되었다.
Object.assign()
Object.assign() 메서드 설명 블로그 포스팅 에 잘 설명되어 있다.
Object.is()
===
연산자와 같으나, 약간의 차이가 있다.
NaN
은 자신과 동일하다. (반면,NaN === NaN
은 거짓이다.)- 양의 0(+0) 과 음의 0(-0) 은 서로 같지 않다. (반면,
+0 === -0
은 참이다.)
console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
console.log(+0 === -0); // true
console.log(NaN === NaN); // false
Object.values()
enumerable
한 값을 제공한다.
const obj = {
a: 1,
b: 2,
c: 3,
};
console.log(Object.values(obj)); // [1, 2, 3]
Object.entries()
enumerable
한 엔트리를 배열로 제공한다.
const obj = {
a: 1,
b: 2,
c: 3,
};
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]
Object.fromEntries()
- 엔트리를 다시 오브젝트로 돌려준다.
- 맵을 객체로 변환하는 데도 편리하다.
console.log(
Object.fromEntries([
["a", 1],
["b", 2],
["c", 3],
])
); // {a: 1, b: 2, c: 3}
Object.getOwnPropertySymbols()
- 심볼을 키로 이용한 프로퍼티가 있는 경우, 키로 이용된 심볼을 반환해준다.
- ES5 메서드
Object.getOwnPropertyNames()
와 이름이 매우 흡사하다.
const mySymbol = Symbol("mySymbol");
const obj = {
[mySymbol]: "hello",
};
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(mySymbol)]
Object.getOwnPropertyDescriptors()
- 열거할 수 없는 프로퍼티와 심볼을 키로 하는 모든 프로퍼티를 포함한 객체를 반환한다.
Object.defineProperties()
const s = Symbol("example");
const o1 = {
// SYMBOL and not enumerable
[s]: "one",
// Getter
get example() {
return this[s];
},
// Setter
set example(value) {
this[s] = value;
},
// 일반 프로퍼티
data: "value",
};
Object.defineProperty(o1, "nonEnum", {
value: 42,
writable: true,
configurable: true,
});
const descriptors = Object.getOwnPropertyDescriptors(o1);
console.log(descriptors);
/*
{example: {…}, data: {…}, nonEnum: {…}, Symbol(example): {…}}
data: {value: 'value', writable: true, enumerable: true, configurable: true}
example: {enumerable: true, configurable: true, get: ƒ, set: ƒ}
nonEnum: {value: 42, writable: true, enumerable: false, configurable: true}
Symbol(example): {value: 'one', writable: true, enumerable: true, configurable: true}
*/
const o2 = Object.defineProperties(o1, descriptors);
console.log(o2);
// {data: 'value', nonEnum: 42, Symbol(example): 'one'}
getOwnPropertyDescriptors()
와 defineProperties()
를 합치면, 모든 속성을 그대로 복사하여 다른 객체에 적용할 수 있다.
Symbol.toPrimitive
이 게시글 에 자세히 정리해두었다.
오브젝트의 속성 순서
이 게시글 에 자세히 정리해두었다.
속성 스프레드 구문
전개 구문, 속성 스프레드 구문이란? 게시글 에 자세히 정리해두었다.
액션 플랜
동적 이름으로 속성 만든다면, computed property
를 활용하자.
과거
let name = "answer";
let obj = {};
obj[name] = 42;
console.log(obj[name]);
개선
let name = "answer";
let obj = {
[name]: 42,
};
console.log(obj[name]);
이름이 같은 변수에서 속성 초기화 시에 단축 구문을 사용하자.
과거
function getMinMax() {
let min, max;
// ...
return { min: min, max: max };
}
개선
function getMinMax() {
let min, max;
// ...
return { min, max };
}
커스텀 확장함수를 사용하기보다 Object.assign()
을 사용하자.
기존 객체의 프로퍼티를 확장하고 싶다면, 함수를 따로 작성하기보다 Object.assign()
을 사용하자.
기존 객체의 속성을 기반으로 새 객체를 만들 때는 Spread Syntax
를 사용하자.
스프레드 구문 을 사용하자.
프로퍼티 이름 충돌이 싫다면, 심볼을 사용하자.
모호한 문자열 말고 심볼을 사용하자.
__proto__
는 이제 그만쓰고 Object.getPrototypeOf()
와 Obhect.setPrototypeOf()
를 사용하자.
__proto__
는 deprecated
되었다.
객체 메서드에는 메서드 구문을 사용하자
- 코드도 더욱 간결해지며,
super
를 통해[[HomeObject]]
에 접근할 수도 있다. super
를 사용하지 않더라도, 자바스크립트 엔진 최적화에 의해 성능 저하는 없다.
기존
const o = {
f: function () {},
};
개선
const o = {
f() {},
};
'자바스크립트 > 웹개발자를 위한 자바스크립트의 모든 것' 카테고리의 다른 글
웹 개발자를 위한 자바스크립트의 모든 것 6장 이터러블, 이터레이터, 제너레이터 (0) | 2023.01.16 |
---|---|
웹 개발자를 위한 자바스크립트의 모든 것 4장 클래스 (0) | 2022.12.29 |
웹 개발자를 위한 자바스크립트의 모든 것 3장 새로운 함수 기능 (0) | 2022.12.22 |
웹 개발자를 위한 자바스크립트의 모든 것 2장 let, const 정리 (0) | 2022.12.18 |
웹 개발자를 위한 자바스크립트의 모든 것 1장 ES2015 - ES2020 정리 (0) | 2022.12.17 |