이 섹션의 첫 번째 장에서 우리는 프로토타입을 설정하는 현대적인 방법이 있다고 언급했습니다.
obj.__proto__
사용하여 프로토타입을 설정하거나 읽는 것은 오래되고 다소 사용되지 않는 것으로 간주됩니다(브라우저 전용을 의미하는 JavaScript 표준의 소위 "부속서 B"로 이동됨).
프로토타입을 얻거나 설정하는 현대적인 방법은 다음과 같습니다.
Object.getPrototypeOf(obj) – obj
의 [[Prototype]]
반환합니다.
Object.setPrototypeOf(obj, proto) – obj
의 [[Prototype]]
proto
로 설정합니다.
눈살을 찌푸리지 않는 __proto__
의 유일한 사용법은 새 객체를 생성할 때 속성으로 사용되는 것입니다: { __proto__: ... }
.
하지만 이에 대한 특별한 방법도 있습니다.
Object.create(proto[, descriptors]) – 주어진 proto
[[Prototype]]
및 선택적 속성 설명자로 사용하여 빈 객체를 생성합니다.
예를 들어:
동물 = { 먹는다: 사실이다 }; // 동물을 프로토타입으로 사용하여 새 개체를 만듭니다. let Rabbit = Object.create(animal); // {__proto__: 동물}과 동일 경고(토끼.먹는다); // 진실 Alert(Object.getPrototypeOf(rabbit) === 동물); // 진실 Object.setPrototypeOf(토끼, {}); // 토끼의 프로토타입을 {}로 변경합니다.
Object.create
메소드는 선택적 두 번째 인수인 속성 설명자를 포함하므로 좀 더 강력합니다.
다음과 같이 새 개체에 추가 속성을 제공할 수 있습니다.
동물 = { 먹는다: 사실이다 }; 토끼 = Object.create(animal, { 점프: { 값: 참 } }); 경고(토끼.점프); // 진실
설명자는 속성 플래그 및 설명자 장에 설명된 것과 동일한 형식입니다.
Object.create
사용하면 for..in
에서 속성을 복사하는 것보다 더 강력한 객체 복제를 수행할 수 있습니다.
복제 = Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) );
이 호출은 열거 가능 및 열거 불가능, 데이터 속성 및 setter/getters 등 모든 속성과 올바른 [[Prototype]]
포함하여 obj
의 정말 정확한 복사본을 만듭니다.
[[Prototype]]
관리하는 방법은 매우 많습니다. 어떻게 그런 일이 일어났나요? 왜?
그것은 역사적인 이유 때문입니다.
프로토타입 상속은 처음부터 언어에 있었지만 이를 관리하는 방법은 시간이 지남에 따라 진화했습니다.
생성자 함수의 prototype
속성은 고대부터 작동해 왔습니다. 주어진 프로토타입으로 객체를 생성하는 가장 오래된 방법입니다.
이후 2012년에 Object.create
표준에 등장했습니다. 주어진 프로토타입으로 객체를 생성하는 기능을 제공했지만 이를 가져오거나 설정하는 기능은 제공하지 않았습니다. 일부 브라우저에서는 개발자에게 더 많은 유연성을 제공하기 위해 사용자가 언제든지 프로토타입을 가져오거나 설정할 수 있는 비표준 __proto__
접근자를 구현했습니다.
이후 2015년에는 __proto__
와 동일한 기능을 수행하기 위해 Object.setPrototypeOf
및 Object.getPrototypeOf
표준에 추가되었습니다. __proto__
사실상 모든 곳에서 구현되었으므로 더 이상 사용되지 않으며 표준의 부록 B, 즉 브라우저가 아닌 환경에서는 선택 사항으로 적용되었습니다.
이후 2022년에는 객체 리터럴 {...}
(부록 B에서 이동)에서 __proto__
공식적으로 사용할 수 있지만 getter/setter obj.__proto__
(여전히 부록 B에 있음)로는 사용할 수 없습니다.
__proto__
getPrototypeOf/setPrototypeOf
함수로 대체된 이유는 무엇입니까?
__proto__
부분적으로 재활되고 {...}
에서 사용이 허용되지만 getter/setter로는 허용되지 않는 이유는 무엇입니까?
이는 __proto__
가 왜 나쁜지 이해해야 하는 흥미로운 질문입니다.
그리고 곧 우리는 답을 얻게 될 것입니다.
속도가 중요하다면 기존 객체의 [[Prototype]]
변경하지 마세요.
기술적으로는 언제든지 [[Prototype]]
가져오거나 설정할 수 있습니다. 그러나 일반적으로 객체 생성 시 한 번만 설정하고 더 이상 수정하지 않습니다. rabbit
animal
에서 상속받으며 이는 변경되지 않습니다.
그리고 JavaScript 엔진은 이를 위해 고도로 최적화되어 있습니다. Object.setPrototypeOf
또는 obj.__proto__=
사용하여 "즉시" 프로토타입을 변경하는 것은 객체 속성 액세스 작업에 대한 내부 최적화를 중단하므로 매우 느린 작업입니다. 따라서 자신이 무엇을 하고 있는지 모르거나 JavaScript 속도가 전혀 중요하지 않은 경우가 아니면 피하세요.
우리가 알고 있듯이 객체는 키/값 쌍을 저장하기 위한 연관 배열로 사용될 수 있습니다.
…그러나 사용자가 제공한 키(예: 사용자가 입력한 사전)를 여기에 저장하려고 하면 흥미로운 결함을 볼 수 있습니다. "__proto__"
제외한 모든 키가 제대로 작동합니다.
예제를 확인해보세요:
obj = {}로 놔두세요; let key = 프롬프트("키가 무엇인가요?", "__proto__"); obj[key] = "일부 값"; 경고(obj[키]); // "어떤 값"이 아닌 [객체 객체]!
여기서 사용자가 __proto__
를 입력하면 4행의 할당은 무시됩니다!
개발자가 아닌 사람에게는 확실히 놀라운 일이지만 우리에게는 꽤 이해할 수 있는 일입니다. __proto__
속성은 특별합니다. 객체이거나 null
이어야 합니다. 문자열은 프로토타입이 될 수 없습니다. 이것이 __proto__
에 대한 문자열 할당이 무시되는 이유입니다.
하지만 우리는 그러한 동작을 구현할 의도가 없었습니다. 그렇죠? 키/값 쌍을 저장하려고 하는데 "__proto__"
라는 키가 제대로 저장되지 않았습니다. 그럼 그건 버그입니다!
여기서 결과는 끔찍하지 않습니다. 그러나 다른 경우에는 obj
에 문자열 대신 객체를 저장할 수 있으며 그러면 프로토타입이 실제로 변경됩니다. 결과적으로, 실행은 전혀 예상치 못한 방식으로 잘못될 것입니다.
더 나쁜 것은 일반적으로 개발자는 그러한 가능성에 대해 전혀 생각하지 않는다는 것입니다. 이로 인해 이러한 버그를 알아차리기 어렵게 만들고 심지어 취약점으로 만들기도 합니다. 특히 JavaScript가 서버 측에서 사용되는 경우 더욱 그렇습니다.
내장 객체 메서드인 obj.toString
에 할당할 때 예기치 않은 일이 발생할 수도 있습니다.
이 문제를 어떻게 피할 수 있습니까?
먼저 일반 객체 대신 저장을 위해 Map
사용하도록 전환하면 모든 것이 괜찮습니다.
let map = new Map(); let key = 프롬프트("키가 무엇인가요?", "__proto__"); map.set(key, "어떤 값"); 경고(map.get(키)); // "일부 값"(의도한 대로)
...하지만 Object
구문은 더 간결하기 때문에 종종 더 매력적입니다.
다행스럽게도 객체를 사용할 수 있는 이유는 언어 제작자들이 오래 전에 이 문제를 고려했기 때문입니다.
우리가 알고 있듯이 __proto__
는 객체의 속성이 아니라 Object.prototype
의 접근자 속성입니다.
따라서 obj.__proto__
읽거나 설정하면 해당 getter/setter가 해당 프로토타입에서 호출되고 [[Prototype]]
가져오거나 설정합니다.
이 튜토리얼 섹션의 시작 부분에서 말했듯이: __proto__
는 [[Prototype]]
에 액세스하는 방법이지 [[Prototype]]
자체는 아닙니다.
이제 객체를 연관 배열로 사용하고 이러한 문제가 발생하지 않으려면 약간의 트릭을 사용하여 수행할 수 있습니다.
let obj = Object.create(null); // 또는: obj = { __proto__: null } let key = 프롬프트("키가 무엇인가요?", "__proto__"); obj[key] = "일부 값"; 경고(obj[키]); // "어떤 값"
Object.create(null)
프로토타입 없이 빈 객체를 생성합니다( [[Prototype]]
은 null
입니다):
따라서 __proto__
에 대해 상속된 getter/setter가 없습니다. 이제 일반 데이터 속성으로 처리되므로 위의 예가 올바르게 작동합니다.
이러한 객체는 일반 일반 객체 {...}
보다 훨씬 간단하기 때문에 "매우 일반" 또는 "순수 사전" 객체라고 부를 수 있습니다.
단점은 이러한 객체에는 내장된 객체 메서드가 없다는 것입니다(예: toString
).
let obj = Object.create(null); 경고(obj); // 오류(toString 없음)
…하지만 연관 배열에는 일반적으로 괜찮습니다.
대부분의 객체 관련 메소드는 Object.keys Object.keys(obj)
Object.something(...)
입니다. 프로토타입에는 없으므로 이러한 객체에 대해 계속 작업합니다.
중국어사전 = Object.create(null); ChineseDictionary.hello = "你好"; ChineseDictionary.bye = "再见"; Alert(Object.keys(chineseDictionary)); //안녕, 안녕
주어진 프로토타입으로 객체를 생성하려면 다음을 사용하세요.
Object.create
모든 설명자가 포함된 객체를 얕은 복사하는 쉬운 방법을 제공합니다.
복제하자 = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
리터럴 구문: { __proto__: ... }
, 여러 속성을 지정할 수 있습니다.
또는 Object.create(proto[, descriptors])를 사용하면 속성 설명자를 지정할 수 있습니다.
프로토타입을 얻거나 설정하는 최신 방법은 다음과 같습니다.
Object.getPrototypeOf(obj) – obj
의 [[Prototype]]
반환합니다( __proto__
getter와 동일).
Object.setPrototypeOf(obj, proto) – obj
의 [[Prototype]]
proto
로 설정합니다( __proto__
setter와 동일).
내장된 __proto__
getter/setter를 사용하여 프로토타입을 가져오거나 설정하는 것은 권장되지 않습니다. 이제 사양의 부록 B에 있습니다.
또한 Object.create(null)
또는 {__proto__: null}
로 생성된 프로토타입 없는 객체도 다루었습니다.
이러한 객체는 (사용자 생성 가능) 키를 저장하기 위해 사전으로 사용됩니다.
일반적으로 객체는 Object.prototype
에서 내장 메서드와 __proto__
getter/setter를 상속하여 해당 키를 "점유"하게 만들고 잠재적으로 부작용을 일으킬 수 있습니다. null
프로토타입을 사용하면 객체가 실제로 비어 있습니다.
중요도: 5
key/value
쌍을 저장하기 위해 Object.create(null)
로 생성된 객체 dictionary
있습니다.
쉼표로 구분된 키 목록을 반환해야 하는 dictionary.toString()
메서드를 여기에 추가합니다. toString
은 객체 위에 for..in
에 표시되어서는 안 됩니다.
작동 방식은 다음과 같습니다.
let Dictionary = Object.create(null); // Dictionary.toString 메소드를 추가하는 코드 // 데이터 추가 Dictionary.apple = "사과"; Dictionary.__proto__ = "테스트"; // __proto__는 여기서 일반 속성 키입니다. // 루프에는 apple과 __proto__만 있습니다. for(사전에 키를 넣음) { 경고(키); // "사과", 그 다음 "__proto__" } // toString이 작동 중입니다. 경고(사전); // "사과,__proto__"
이 메서드는 Object.keys
사용하여 열거 가능한 모든 키를 가져와 해당 목록을 출력할 수 있습니다.
toString
열거 불가능하게 만들려면 속성 설명자를 사용하여 정의해 보겠습니다. Object.create
구문을 사용하면 두 번째 인수로 속성 설명자가 있는 객체를 제공할 수 있습니다.
let Dictionary = Object.create(null, { toString: { // toString 속성을 정의합니다. value() { // 값은 함수입니다. return Object.keys(this).join(); } } }); Dictionary.apple = "사과"; Dictionary.__proto__ = "테스트"; // apple과 __proto__가 루프에 있습니다. for(사전에 키를 넣음) { 경고(키); // "사과", 그 다음 "__proto__" } // toString별로 쉼표로 구분된 속성 목록 경고(사전); // "사과,__proto__"
설명자를 사용하여 속성을 생성할 때 해당 플래그는 기본적으로 false
입니다. 따라서 위 코드에서 dictionary.toString
은 열거할 수 없습니다.
검토하려면 속성 플래그 및 설명자 장을 참조하세요.
중요도: 5
새로운 rabbit
개체를 만들어 보겠습니다.
함수 토끼(이름) { this.name = 이름; } Rabbit.prototype.sayHi = function() { 경고(this.name); }; let Rabbit = new Rabbit("토끼");
이러한 호출은 동일한 작업을 수행합니까, 아니면 수행하지 않습니까?
토끼.sayHi(); Rabbit.prototype.sayHi(); Object.getPrototypeOf(rabbit).sayHi(); Rabbit.__proto__.sayHi();
첫 번째 호출에는 this == rabbit
있고, 다른 호출에는 실제로 점 앞에 있는 객체이기 때문에 Rabbit.prototype
과 this
합니다.
따라서 첫 번째 호출에만 Rabbit
표시되고, 다른 호출에는 undefined
표시됩니다.
함수 토끼(이름) { this.name = 이름; } Rabbit.prototype.sayHi = function() { 경고(this.name); } let Rabbit = new Rabbit("토끼"); 토끼.sayHi(); // 토끼 Rabbit.prototype.sayHi(); // 한정되지 않은 Object.getPrototypeOf(rabbit).sayHi(); // 한정되지 않은 Rabbit.__proto__.sayHi(); // 한정되지 않은