"prototype"
속성은 JavaScript 자체의 핵심에서 널리 사용됩니다. 모든 내장 생성자 함수가 이를 사용합니다.
먼저 세부 사항을 살펴본 다음 이를 사용하여 내장 개체에 새로운 기능을 추가하는 방법을 살펴보겠습니다.
빈 객체를 출력한다고 가정해 보겠습니다.
obj = {}로 놔두세요; 경고(obj); // "[객체 객체]" ?
"[object Object]"
문자열을 생성하는 코드는 어디에 있습니까? 내장된 toString
메소드인데 어디에 있나요? obj
비어 있습니다!
…그러나 짧은 표기법 obj = {}
obj = new Object()
와 동일합니다. 여기서 Object
toString
및 기타 메서드를 사용하여 거대한 객체를 참조하는 자체 prototype
이 있는 내장 객체 생성자 함수입니다.
진행 상황은 다음과 같습니다.
new Object()
가 호출될 때(또는 리터럴 객체 {...}
생성될 때), 그것의 [[Prototype]]
이전 장에서 논의한 규칙에 따라 Object.prototype
으로 설정됩니다:
따라서 obj.toString()
이 호출되면 해당 메서드는 Object.prototype
에서 가져옵니다.
우리는 이를 다음과 같이 확인할 수 있습니다:
obj = {}로 놔두세요; 경고(obj.__proto__ === Object.prototype); // 진실 경고(obj.toString === obj.__proto__.toString); //진실 경고(obj.toString === Object.prototype.toString); //진실
Object.prototype
위의 체인에는 더 이상 [[Prototype]]
이 없습니다.
경고(Object.prototype.__proto__); // 널
Array
, Date
, Function
등과 같은 기타 내장 객체도 프로토타입에 메서드를 유지합니다.
예를 들어, [1, 2, 3]
배열을 만들 때 기본 new Array()
생성자가 내부적으로 사용됩니다. 따라서 Array.prototype
프로토타입이 되고 메소드를 제공합니다. 이는 매우 메모리 효율적입니다.
사양에 따라 모든 내장 프로토타입의 상단에는 Object.prototype
있습니다. 그래서 어떤 사람들은 "모든 것은 객체로부터 상속된다"고 말합니다.
전체 그림은 다음과 같습니다(3개의 내장 기능이 적합함).
프로토타입을 수동으로 확인해 보겠습니다.
arr = [1, 2, 3]으로 설정합니다. // Array.prototype에서 상속되나요? 경고( arr.__proto__ === Array.prototype ); // 진실 // 그러면 Object.prototype에서? 경고( arr.__proto__.__proto__ === Object.prototype ); // 진실 // 맨 위에는 null이 있습니다. 경고( arr.__proto__.__proto__.__proto__ ); // 널
프로토타입의 일부 메소드는 겹칠 수 있습니다. 예를 들어 Array.prototype
에는 쉼표로 구분된 요소를 나열하는 자체 toString
있습니다.
arr = [1, 2, 3]이라고 놔두세요. 경고(arr); // 1,2,3 <-- Array.prototype.toString의 결과
이전에 본 것처럼 Object.prototype
에는 toString
도 있지만 Array.prototype
이 체인에서 더 가깝기 때문에 배열 변형이 사용됩니다.
Chrome 개발자 콘솔과 같은 브라우저 내 도구에도 상속이 표시됩니다( 내장 개체에 console.dir
사용해야 할 수도 있음).
다른 내장 객체도 같은 방식으로 작동합니다. 심지어 함수 – 이는 내장 Function
생성자의 객체이며 해당 메서드( call
/ apply
및 기타)는 Function.prototype
에서 가져옵니다. 함수에는 자체 toString
도 있습니다.
함수 f() {} Alert(f.__proto__ == Function.prototype); // 진실 Alert(f.__proto__.__proto__ == Object.prototype); // true, 객체에서 상속
가장 복잡한 일은 문자열, 숫자, 부울에서 발생합니다.
우리가 기억하는 것처럼 그것들은 객체가 아닙니다. 그러나 해당 속성에 액세스하려고 하면 내장 생성자 String
, Number
및 Boolean
사용하여 임시 래퍼 개체가 생성됩니다. 그들은 방법을 제공하고 사라집니다.
이러한 객체는 우리에게 보이지 않게 생성되며 대부분의 엔진은 이를 최적화하지만 사양에서는 이를 정확하게 이렇게 설명합니다. 이러한 객체의 메서드는 String.prototype
, Number.prototype
및 Boolean.prototype
으로 사용 가능한 프로토타입에도 상주합니다.
null
및 undefined
값에는 객체 래퍼가 없습니다.
특수 값인 null
과 undefined
구별됩니다. 객체 래퍼가 없으므로 메서드와 속성을 사용할 수 없습니다. 그리고 해당 프로토타입도 없습니다.
기본 프로토타입을 수정할 수 있습니다. 예를 들어 String.prototype
에 메소드를 추가하면 모든 문자열에서 사용할 수 있게 됩니다.
String.prototype.show = 함수() { 경고(이); }; "붐!".show(); // 붐!
개발 과정에서 우리는 갖고 싶은 새로운 내장 메서드에 대한 아이디어를 가질 수 있으며, 이를 네이티브 프로토타입에 추가하고 싶은 유혹을 받을 수도 있습니다. 그러나 그것은 일반적으로 나쁜 생각입니다.
중요한:
프로토타입은 전역적이므로 충돌이 발생하기 쉽습니다. 두 라이브러리가 String.prototype.show
메서드를 추가하면 그 중 하나가 다른 라이브러리의 메서드를 덮어쓰게 됩니다.
따라서 일반적으로 기본 프로토타입을 수정하는 것은 나쁜 생각으로 간주됩니다.
현대 프로그래밍에서는 네이티브 프로토타입 수정이 승인되는 경우가 단 한 번뿐입니다. 폴리필링이군요.
폴리필링은 JavaScript 사양에 존재하지만 아직 특정 JavaScript 엔진에서 지원되지 않는 메서드를 대체하는 용어입니다.
그런 다음 이를 수동으로 구현하고 내장 프로토타입을 채울 수 있습니다.
예를 들어:
if (!String.prototype.repeat) { // 해당 메소드가 없는 경우 //프로토타입에 추가 String.prototype.repeat = 함수(n) { // 문자열을 n번 반복 // 실제로 코드는 그보다 조금 더 복잡해야 합니다. // (전체 알고리즘은 사양에 있음) // 하지만 불완전한 폴리필이라도 충분하다고 간주되는 경우가 많습니다. return new Array(n + 1).join(this); }; } 경고( "라".repeat(3) ); // 라라라
데코레이터와 전달, 호출/적용 장에서 메소드 차용에 대해 이야기했습니다.
이때는 한 개체에서 메서드를 가져와 다른 개체에 복사하는 경우입니다.
네이티브 프로토타입의 일부 방법은 종종 차용됩니다.
예를 들어 배열과 유사한 객체를 만드는 경우 일부 Array
메서드를 해당 객체에 복사할 수 있습니다.
예:
obj = {라고 놔두세요 0: "안녕하세요", 1: "세계!", 길이: 2, }; obj.join = Array.prototype.join; 경고( obj.join(',') ); // 안녕하세요, 세상!
내장 join
방법의 내부 알고리즘은 올바른 인덱스와 length
속성에만 관심이 있기 때문에 작동합니다. 객체가 실제로 배열인지 확인하지 않습니다. 많은 내장 메소드가 이와 같습니다.
또 다른 가능성은 obj.__proto__
Array.prototype
으로 설정하여 상속하는 것입니다. 그러면 모든 Array
메서드를 obj
에서 자동으로 사용할 수 있습니다.
그러나 obj
이미 다른 객체로부터 상속받은 경우에는 불가능합니다. 한 번에 하나의 개체에서만 상속할 수 있다는 점을 기억하세요.
차용 방법은 유연하며 필요한 경우 다른 개체의 기능을 혼합할 수 있습니다.
모든 내장 객체는 동일한 패턴을 따릅니다:
메소드는 프로토타입( Array.prototype
, Object.prototype
, Date.prototype
등)에 저장됩니다.
객체 자체는 데이터(배열 항목, 객체 속성, 날짜)만 저장합니다.
또한 프리미티브는 래퍼 개체의 프로토타입( Number.prototype
, String.prototype
및 Boolean.prototype
에 메서드를 저장합니다. undefined
및 null
에만 래퍼 개체가 없습니다.
내장된 프로토타입을 수정하거나 새로운 방법으로 채울 수 있습니다. 그러나 변경하는 것은 권장되지 않습니다. 허용되는 유일한 경우는 아마도 새로운 표준을 추가하지만 아직 JavaScript 엔진에서 지원하지 않는 경우일 것입니다.
중요도: 5
모든 함수의 프로토타입에 ms
밀리초 후에 함수를 실행하는 defer(ms)
메서드를 추가합니다.
그렇게 하고 나면 다음과 같은 코드가 작동합니다.
함수 f() { Alert("안녕하세요!"); } f.defer(1000); // "안녕하세요!"를 표시합니다. 1초 후
Function.prototype.defer = 함수(ms) { setTimeout(this, ms); }; 함수 f() { Alert("안녕하세요!"); } f.defer(1000); // "안녕하세요!"를 표시합니다. 1초 후
중요도: 4
모든 함수의 프로토타입에 래퍼를 반환하는 defer(ms)
메서드를 추가하여 ms
밀리초만큼 호출을 지연시킵니다.
작동 방식에 대한 예는 다음과 같습니다.
함수 f(a, b) { 경고( a + b ); } f.defer(1000)(1, 2); // 1초 후에 3을 표시합니다.
인수는 원래 함수에 전달되어야 합니다.
Function.prototype.defer = 함수(ms) { f=이것이라고 하자; 반환 함수(...args) { setTimeout(() => f.apply(this, args), ms); } }; // 확인해보세요 함수 f(a, b) { 경고( a + b ); } f.defer(1000)(1, 2); // 1초 후에 3을 표시합니다.
참고: f.apply
에서 this
사용하여 객체 메서드에 대한 장식 작업을 수행합니다.
따라서 래퍼 함수가 객체 메서드로 호출되면 this
원래 메서드 f
로 전달됩니다.
Function.prototype.defer = 함수(ms) { f=이것이라고 하자; 반환 함수(...args) { setTimeout(() => f.apply(this, args), ms); } }; 사용자 = { 이름: "존", 안녕하세요() { 경고(this.name); } } user.sayHi = user.sayHi.defer(1000); user.sayHi();