>웹 프론트엔드 >JS 튜토리얼 >JavaScript_javascript 팁의 프로토타입 상속에 대한 자세한 설명

JavaScript_javascript 팁의 프로토타입 상속에 대한 자세한 설명

WBOY
WBOY원래의
2016-05-16 16:14:051098검색

자바스크립트는 객체지향 언어입니다. JavaScript에는 모든 것이 객체라는 매우 고전적인 말이 있습니다. 객체지향이므로 캡슐화, 상속, 다형성이라는 객체지향의 세 가지 주요 특성을 갖습니다. 여기서 말하는 것은 JavaScript 상속이고, 나머지 두 가지에 대해서는 나중에 이야기하겠습니다.

JavaScript 상속은 C 상속과 다릅니다. C 상속은 클래스를 기반으로 하는 반면 JavaScript 상속은 프로토타입을 기반으로 합니다.

이제 문제가 발생합니다.

프로토타입이란 무엇인가요? 프로토타입의 경우 객체의 속성과 메서드도 저장하는 C 클래스를 참조할 수 있습니다. 예를 들어 간단한 객체를 작성해 보겠습니다

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
This.name = 이름;
}
Animal.prototype.setName = 함수(이름) {
This.name = 이름;
}
var Animal = new Animal("왕왕");

우리는 이것이 속성 이름과 메소드 setName을 갖는 객체 Animal임을 알 수 있습니다. 메소드를 추가하는 등 프로토타입이 수정되면 객체의 모든 인스턴스가 이 메소드를 공유한다는 점에 유의해야 합니다. 예를 들어

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
This.name = 이름;
}
var Animal = new Animal("왕왕");

현재 동물에는 이름 속성만 있습니다. 한 문장을 추가하면

코드 복사 코드는 다음과 같습니다.

Animal.prototype.setName = 함수(이름) {
This.name = 이름;
}

이때 동물에도 setName 메소드가 있습니다.

이 복사본 상속 - 빈 객체에서 시작 JS의 기본 유형 중에는 객체라는 것이 있으며 가장 기본적인 인스턴스는 빈 객체, 즉 새 객체를 직접 호출하여 생성된 인스턴스입니다. () 또는 리터럴 { }를 사용하여 선언됩니다. 빈 개체는 미리 정의된 속성과 메서드만 있는 "깨끗한 개체"이며, 다른 모든 개체는 빈 개체에서 상속되므로 모든 개체에는 이러한 미리 정의된 속성과 메서드가 있습니다. 프로토타입은 실제로 객체 인스턴스입니다. 프로토타입의 의미는 생성자에 프로토타입 객체 A가 있는 경우 생성자가 만든 인스턴스를 A에서 복사해야 한다는 것입니다. 인스턴스는 객체 A에서 복사되므로 인스턴스는 A의 모든 속성, 메서드 및 기타 속성을 상속해야 합니다. 그렇다면 복제는 어떻게 달성됩니까? 방법 1: 생성 복사 인스턴스가 생성될 때마다 프로토타입에서 인스턴스가 복사됩니다. 새 인스턴스와 프로토타입은 동일한 메모리 공간을 차지합니다. 이로 인해 obj1 및 obj2가 해당 프로토타입과 "완전히 일치"하게 되지만 매우 비경제적이기도 합니다. 메모리 공간 소비가 급격히 증가합니다. 사진과 같이:


방법 2: 기록 중 복사 이 전략은 시스템에 대한 일관된 트릭인 기록 중 복사에서 비롯됩니다. 이런 종류의 속임수의 전형적인 예는 운영 체제의 DDL(동적 링크 라이브러리)입니다. 이 라이브러리의 메모리 영역은 항상 쓰기 시 복사됩니다. 사진과 같이:


obj1과 obj2가 프로토타입과 동일하다는 점만 시스템에 표시하면 되므로 읽을 때 지침에 따라 프로토타입을 읽으면 됩니다. 객체(예: obj2)의 속성을 작성해야 할 경우 프로토타입 이미지를 복사하고 향후 작업을 이 이미지로 지정합니다. 사진과 같이:


이 방법의 장점은 인스턴스를 생성하고 속성을 읽을 때 메모리 오버헤드가 많이 필요하지 않다는 것입니다. 처음 작성할 때 일부 코드만 사용하여 메모리를 할당하므로 일부 코드와 메모리 오버헤드가 발생합니다. . 하지만 이미지에 액세스하는 효율성이 프로토타입에 액세스하는 것과 같기 때문에 더 이상 그러한 오버헤드가 없습니다. 그러나 쓰기 작업을 자주 수행하는 시스템의 경우 이 방법은 이전 방법보다 경제적이지 않습니다. 방법 3: 읽기 순회 이 방법은 복제 세분성을 프로토타입에서 멤버로 변경합니다. 이 방식의 특징은 인스턴스의 멤버를 작성할 때만 멤버 정보를 인스턴스 이미지에 복사한다는 점이다. 예를 들어(obj2.value=10) 객체 속성을 작성할 때 value라는 속성 값이 생성되어 obj2 객체의 구성원 목록에 배치됩니다. 사진을 보세요:

obj2는 여전히 프로토타입을 가리키는 참조이며 작업 중에 프로토타입과 동일한 크기의 객체 인스턴스가 생성되지 않음을 알 수 있습니다. 이러한 방식으로 쓰기 작업으로 인해 큰 메모리 할당이 발생하지 않으므로 메모리 사용량이 경제적이 됩니다. 차이점은 obj2(및 모든 객체 인스턴스)가 구성원 목록을 유지해야 한다는 것입니다. 이 멤버 목록은 두 가지 규칙을 따릅니다: 읽을 때 먼저 액세스되도록 보장됩니다. 객체에 속성이 지정되지 않으면 프로토타입이 비어 있거나 속성을 찾을 때까지 객체의 전체 프로토타입 체인을 순회하려고 시도합니다. 프로토타입 체인은 나중에 논의될 것입니다. 분명히 세 가지 방법 중 읽기 순회가 가장 좋은 성능을 보입니다. 따라서 JavaScript의 프로토타입 상속은 읽기 순회입니다. constructor C에 익숙한 사람들은 최상위 객체의 코드를 읽고 나면 분명히 혼란스러울 것입니다. 결국 class 키워드가 없으면 이해하기 더 쉽습니다. 단지 다른 키워드인 function 키워드가 있습니다. 하지만 생성자는 어떻습니까? 사실 자바스크립트에도 비슷한 생성자가 있는데 이를 생성자라고 부릅니다. new 연산자를 사용할 때 생성자는 실제로 호출되었으며 이는 객체에 바인딩됩니다. 예를 들어 다음 코드를 사용합니다

코드 복사 코드는 다음과 같습니다.

var 동물 = 동물("왕왕");

동물은 정의되지 않습니다. 어떤 사람들은 반환 값이 물론 정의되지 않았다고 말할 것입니다. 그런 다음 Animal의 객체 정의를 변경하면:

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
This.name = 이름;
이것을 돌려주세요;
}

지금은 어떤 동물인지 맞춰보세요?
이때 동물은 창이 된다. 이름속성을 가지도록 창이 확장된다는 점이다. 지정하지 않을 경우 최상위 변수인 window가 기본값으로 설정되기 때문입니다. new 키워드를 호출해야만 생성자를 올바르게 호출할 수 있습니다. 그렇다면 사용자가 새 키워드를 놓치지 않도록 하려면 어떻게 해야 할까요? 몇 가지 작은 변화를 줄 수 있습니다:

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
If(!(이 인스턴스의 Animal)) {
          새 동물(이름) 반환
}
This.name = 이름;
}

이렇게 하면 안전할 것입니다. 생성자는 인스턴스가 속한 개체를 나타내는 데에도 사용됩니다. instanceof를 사용하여 판단할 수 있지만, 상속 중에 조상 객체와 실제 객체 모두에 대해 인스턴스가 true를 반환하므로 적합하지 않습니다. new를 사용하여 생성자를 호출하면 기본적으로 현재 객체를 가리킵니다.

코드 복사 코드는 다음과 같습니다.

console.log(Animal.prototype.constructor === Animal) // true

다르게 생각할 수 있습니다. 함수가 초기화될 때 프로토타입에는 전혀 값이 없습니다. 구현은 다음과 같은 논리일 수 있습니다

//__proto__를 함수의 내장 멤버로 설정하고 get_prototyoe()는 해당 메서드입니다

코드 복사 코드는 다음과 같습니다.

var __proto__ = null;
함수 get_prototype() {
If(!__proto__) {
​​​​ __proto__ = new Object();
         __proto__.constructor = 이;
}
__proto__ 반환;
}

이것의 장점은 함수가 선언될 때마다 객체 인스턴스를 생성하지 않아도 되어 오버헤드가 절약된다는 것입니다. 생성자는 수정될 수 있으며 이에 대해서는 나중에 설명합니다. 프로토타입 기반 상속 상속이 무엇인지는 다들 아실 거라 믿기 때문에 IQ의 하한선을 자랑하지는 않겠습니다.

JS 상속에는 여러 유형이 있는데, 여기서는 두 가지 유형이 있습니다.

1. 방법 1 이 방법이 가장 일반적으로 사용되며 보안이 더 좋습니다. 먼저 두 개의 객체를 정의해 보겠습니다

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
This.name = 이름;
}
기능 개(나이) {
이.나이=나이;
}
var dog = new Dog(2);

상속을 구성하는 방법은 매우 간단합니다. 하위 개체의 프로토타입이 상위 개체의 인스턴스를 가리키도록 하면 됩니다(객체가 아닌 인스턴스라는 점에 유의하세요)

코드 복사 코드는 다음과 같습니다.

Dog.prototype = new Animal("왕왕");

이때 개는 이름과 나이라는 두 가지 속성을 갖게 됩니다. 그리고 개에 인스턴스 오브 연산자

를 사용하면

코드 복사 코드는 다음과 같습니다.

console.log(dog 인스턴스of Animal); // true
console.log(dog 인스턴스of Dog); // false

이렇게 하면 상속은 이루어지지만 작은 문제가 있습니다

코드 복사 코드는 다음과 같습니다.

console.log(Dog.prototype.constructor === Animal) // true
console.log(Dog.prototype.constructor === Dog); // false

생성자가 가리키는 개체가 변경되어 목적에 맞지 않는 것을 볼 수 있습니다. 새 인스턴스가 누구에게 속해 있는지 판단할 수 없습니다. 그러므로 다음 문장을 추가할 수 있습니다:

코드 복사 코드는 다음과 같습니다.

Dog.prototype.constructor = 개;

다시 살펴보겠습니다.

코드 복사 코드는 다음과 같습니다.

console.log(dog 인스턴스of Animal); // false
console.log(dog 인스턴스of Dog); // true

완료. 이 방법은 프로토타입 체인 유지 관리의 일부이며 아래에서 자세히 설명합니다. 2. 방법 2 이 방법에는 장점과 단점이 있지만 장점보다 단점이 더 많습니다. 먼저 코드를 살펴보겠습니다

코드 복사 코드는 다음과 같습니다.

function Animal(이름) {
This.name = 이름;
}
Animal.prototype.setName = 함수(이름) {
This.name = 이름;
}
기능 개(나이) {
이.나이=나이;
}
Dog.prototype = Animal.prototype;

이렇게 하면 프로토타입 복사가 이루어집니다.

이 방법의 장점은 개체를 인스턴스화할 필요가 없다는 것입니다(방법 1에 비해). 이는 리소스를 절약합니다. 단점도 위와 같은 문제, 즉 생성자가 부모 객체를 가리키고 있다는 점 외에도 부모 객체의 프로토타입에서 선언한 속성과 메서드만 복사할 수 있다는 점입니다. 즉, 위 코드에서는 Animal 객체의 name 속성은 복사할 수 없으나, setName 메소드는 복사할 수 있습니다. 가장 치명적인 점은 자식 개체의 프로토타입을 수정하면 부모 개체의 프로토타입에 영향을 미친다는 것입니다. 즉, 두 개체에서 선언한 인스턴스가 모두 영향을 받습니다. 따라서 이 방법은 권장되지 않습니다.

프로토타입 체인

상속을 작성해 본 사람이라면 누구나 상속이 여러 수준에서 상속될 수 있다는 것을 알고 있습니다. JS에서는 이것이 프로토타입 체인을 구성합니다. 프로토타입 체인은 위에서 여러 번 언급했는데, 프로토타입 체인이란 무엇일까요? 인스턴스에는 최소한 JavaScript 객체 시스템의 기초가 되는 프로토타입을 가리키는 proto 속성이 있어야 합니다. 그러나 이 속성은 보이지 않습니다. 생성자의 프로토타입으로 구성된 "생성자 프로토타입 체인"(보통 "프로토타입 체인"이라고 함)과 구별하기 위해 "내부 프로토타입 체인"이라고 부릅니다. 먼저 위 코드에 따라 간단한 상속 관계를 구성해 보겠습니다.

코드 복사 코드는 다음과 같습니다.

함수 동물(이름) {
This.name = 이름;
}
기능 개(나이) {
이.나이=나이;
}
var Animal = new Animal("왕왕");
Dog.prototype = 동물;
var dog = new Dog(2);

앞서 언급했듯이 모든 개체는 빈 개체를 상속합니다. 따라서 우리는 프로토타입 체인을 구축했습니다:


자식 개체의 프로토타입이 부모 개체의 인스턴스를 가리키며 생성자 프로토타입 체인을 형성하는 것을 볼 수 있습니다. 자식 인스턴스의 내부 프로토 개체도 부모 개체의 인스턴스를 가리키며 내부 프로토타입 체인을 형성합니다. 특정 속성을 찾아야 하는 경우 코드는

과 유사합니다.

코드 복사 코드는 다음과 같습니다.

함수 getAttrFromObj(attr, obj) {
If(typeof(obj) === "객체") {
        var proto = obj;
​​​​동안(프로토) {
If(proto.hasOwnProperty(attr)) {
                     proto 반환[attr];
            }
              proto = proto.__proto__;
}
}
정의되지 않은 상태로 반환;
}

이 예에서는 dog에서 name 속성을 찾으면 dog의 회원 목록에서 검색됩니다. 물론 이제 dog의 회원 목록에는 나이만 있으므로 검색되지 않습니다. 그런 다음 프로토타입 체인, 즉 .proto가 가리키는 인스턴스, 즉 동물에서 name 속성을 찾아 반환합니다. 존재하지 않는 속성을 찾고 있는데 동물에서 찾을 수 없으면 계속해서 .proto를 따라 검색하고 빈 개체를 찾을 수 없으면 계속해서 .proto를 따라 검색합니다. 빈 개체입니다. proto는 null을 가리키며 종료를 찾습니다.

프로토타입 체인 유지 관리 프로토타입 상속에 관해 방금 이야기했을 때 질문이 제기되었습니다. 상속을 구성하기 위해 메서드 1을 사용할 때 자식 개체 인스턴스의 생성자는 부모 개체를 가리킵니다. 이것의 장점은 생성자 속성을 통해 프로토타입 체인에 접근할 수 있다는 점이지만 단점도 분명합니다. 객체, 즉 객체가 생성하는 인스턴스는 자신을 가리켜야 합니다. 즉,

코드 복사 코드는 다음과 같습니다.

(new obj()).prototype.constructor === obj

그런 다음 프로토타입 속성을 재정의하면 하위 개체에 의해 생성된 인스턴스의 생성자가 자신을 가리키지 않습니다! 이는 생성자의 원래 의도에 어긋납니다. 위에서 해결책을 언급했습니다.

코드 복사 코드는 다음과 같습니다.

Dog.prototype = new Animal("왕왕");
Dog.prototype.constructor = 개;

문제 없을 것 같습니다. 그러나 실제로 이것은 새로운 문제를 가져옵니다. 왜냐하면 우리는 프로토타입 체인을 역추적할 수 없다는 것을 알게 될 것입니다. 왜냐하면 우리는 부모 객체를 찾을 수 없고 내부 프로토타입 체인의 .proto 속성에 접근할 수 없기 때문입니다. 따라서 SpiderMonkey는 향상된 솔루션을 제공합니다. 생성된 객체에 __proto__라는 속성을 추가하는 것입니다. 이 속성은 항상 생성자가 사용하는 프로토타입을 가리킵니다. 이런 방식으로 생성자를 수정해도 __proto__ 값에 영향을 주지 않으므로 생성자를 유지 관리하기가 더 쉬워집니다.

그러나 두 가지 문제가 더 있습니다.

__proto__는 재정의 가능하므로 사용할 때 여전히 위험이 있습니다

__proto__는 spiderMonkey의 특수 처리이므로 다른 엔진(JScript 등)에서는 사용할 수 없습니다.

또한 프로토타입의 생성자 속성을 유지하고 서브클래스 생성자 함수 내에서 인스턴스의 생성자 속성을 초기화하는 또 다른 방법도 있습니다.

코드는 다음과 같습니다. 하위 개체를 다시 작성합니다

코드 복사 코드는 다음과 같습니다.

기능 개(나이) {
This.constructor = 인수.callee;
이.나이=나이;
}
Dog.prototype = new Animal("왕왕");

이런 방식으로 모든 하위 개체 인스턴스의 생성자는 개체를 올바르게 가리키고 프로토타입의 생성자는 상위 개체를 가리킵니다. 이 방법은 인스턴스가 생성될 때마다 생성자 속성을 다시 작성해야 하기 때문에 상대적으로 비효율적이지만, 이 방법이 이전 모순을 효과적으로 해결할 수 있다는 것은 의심의 여지가 없습니다. ES5는 이러한 상황을 고려하여 이 문제를 완전히 해결합니다. 생성자에 액세스하거나 외부 프로토타입 체인을 유지하지 않고도 언제든지 Object.getPrototypeOf()를 사용하여 객체의 실제 프로토타입을 얻을 수 있습니다. 따라서 이전 섹션에서 언급한 객체 속성을 찾으려면 다음과 같이 다시 작성할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

함수 getAttrFromObj(attr, obj) {
If(typeof(obj) === "객체") {
하세요 {
          var proto = Object.getPrototypeOf(dog);
If(프로토[속성]) {
                     proto 반환[attr];
            }
}
​​​​동안(프로토);
}
정의되지 않은 상태로 반환;
}

물론 이 방법은 ES5를 지원하는 브라우저에서만 사용할 수 있습니다. 이전 버전과의 호환성을 위해서는 여전히 이전 방법을 고려해야 합니다. 더 적절한 방법은 이 두 가지 방법을 통합하고 캡슐화하는 것입니다. 독자들은 이에 대해 매우 잘 알고 있으므로 여기서는 설명하지 않겠습니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.