JavaScript는 객체 지향 언어입니다. JavaScript에는 매우 고전적인 말이 있습니다. 모든 것이 대상입니다. 객체 지향적이므로 캡슐화, 상속 및 다형성의 객체 지향의 세 가지 주요 특성이 있습니다. 여기서 우리는 JavaScript의 상속에 대해 이야기하고 나중에 다른 두 가지에 대해 이야기 할 것입니다.
JavaScript의 상속은 C ++의 상속과 거의 동일하지 않습니다. C ++의 상속은 클래스를 기반으로하며 JavaScript의 상속은 프로토 타입을 기반으로합니다.
이제 문제가 여기 있습니다.
프로토 타입은 무엇입니까? 프로토 타입의 경우 C ++의 클래스를 참조하고 객체의 속성과 방법을 저장할 수 있습니다. 예를 들어, 간단한 개체를 작성해 봅시다
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
this.name = 이름;
}
Animal.prototype.setname = function (name) {
this.name = 이름;
}
var 동물 = 새로운 동물 ( "Wangwang");
우리는 이것이 객체 동물임을 알 수 있습니다.이 동물은 속성 이름과 메소드 SetName이 있습니다. 메소드 추가와 같은 프로토 타입이 수정되면 객체의 모든 인스턴스는이 메소드를 공유합니다. 예를 들어
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
this.name = 이름;
}
var 동물 = 새로운 동물 ( "Wangwang");
현재 동물은 이름 속성 만 가지고 있습니다. 문장을 추가하면
코드 사본은 다음과 같습니다.
Animal.prototype.setname = function (name) {
this.name = 이름;
}
현재 Animal은 SetName 메소드도 갖습니다.
상속 사본 - 빈 객체에서 시작하여 JS의 기본 유형 중에는 객체라는 유형이 있으며 가장 기본적인 인스턴스는 빈 객체, 즉 new Object ()를 직접 호출하여 생성 된 인스턴스라는 것을 알고 있습니다. 빈 객체는 사전 정의 된 속성과 메소드 만있는 "깨끗한 객체"이며 다른 모든 객체는 빈 객체에서 상속되므로 모든 객체에는 사전 정의 된 속성과 메소드가 있습니다. 프로토 타입은 실제로 객체 인스턴스입니다. 프로토 타입의 의미는 다음과 같습니다. 생성자에 프로토 타입 객체 A가있는 경우 생성자에 의해 생성 된 인스턴스는 A에서 복사되어야합니다. 인스턴스는 객체 A에서 복사되므로 인스턴스는 A의 모든 특성, 메소드 및 기타 속성을 상속해야합니다. 따라서 복제는 어떻게 구현됩니까? 메소드 1 : 구성 복사 된 구성된 인스턴스는 프로토 타입에서 복사되며 새 인스턴스는 프로토 타입과 동일한 메모리 공간을 차지합니다. 이로 인해 OBJ1과 OBJ2가 프로토 타입으로 "완전히 일관되게"만들어 지지만 매우 비 경제적입니다. 메모리 공간의 소비는 빠르게 증가 할 것입니다. 그림과 같이 :
방법 2 : 작성시이 전략은 일관된 속임수 시스템의 기술에서 비롯됩니다. 이러한 종류의 사기의 전형적인 예는 운영 체제의 동적 링크 라이브러리 (DDL)이며, 메모리 영역은 항상 쓰기에 복사됩니다. 그림과 같이 :
우리는 단지 OBJ1과 OBJ2가 프로토 타입과 동일하다는 시스템에서 지정하면되므로 읽을 때 프로토 타입을 읽기 위해 지침을 따라야합니다. OBJ2와 같은 객체의 속성을 작성 해야하는 경우 프로토 타입 이미지를 복사하고 후속 작업이 이미지를 가리 킵니다. 그림과 같이 :
이 방법의 장점은 인스턴스를 만들고 속성을 읽을 때 많은 메모리 오버 헤드가 필요하지 않다는 것입니다. 우리는 처음 쓸 때 메모리를 할당하기 위해 일부 코드 만 사용하고 코드와 메모리 오버 헤드를 가져옵니다. 그러나 이미지에 액세스하고 프로토 타입에 액세스하는 효율성이 일관되기 때문에 그러한 오버 헤드는 없을 것입니다. 그러나 종종 작업을 작성하는 시스템의 경우이 방법은 이전 방법보다 경제적이지 않습니다. 방법 3 : 트래버스 읽기이 방법은 사본의 세분성을 프로토 타입에서 멤버로 바꿉니다. 이 메소드는 인스턴스 멤버를 작성할 때만 멤버 정보를 인스턴스 이미지에 복사하는 것이 특징입니다. 객체 속성 (예 : OBJ2.Value = 10)을 작성할 때 이름 값의 속성 값이 생성되어 OBJ2 객체의 멤버 목록에 배치됩니다. 사진을보세요 :
OBJ2는 여전히 프로토 타입에 대한 참조이며, 작동 중에 프로토 타입과 동일한 크기의 객체 인스턴스는 없음을 발견 할 수 있습니다. 이런 식으로 쓰기 작업은 많은 양의 메모리 할당으로 이어지지 않으므로 메모리 사용량은 경제적 인 것처럼 보입니다. 차이점은 OBJ2 (및 모든 객체 인스턴스)가 멤버 목록을 유지해야한다는 것입니다. 이 멤버 목록은 두 가지 규칙을 따릅니다. 읽을 때 먼저 액세스 할 수 있습니다. 객체에 속성이 지정되지 않은 경우 프로토 타입이 비어 있거나 속성이 발견 될 때까지 객체의 전체 프로토 타입 체인을 가로 지르십시오. 프로토 타입 체인은 나중에 논의 될 것입니다. 분명히, 세 가지 방법 중에서 읽기 Traversal은 최고의 성능입니다. 따라서 JavaScript의 프로토 타입 상속은 트래버스를 읽습니다. C ++에 익숙한 생성자는 상단 객체의 코드를 읽은 후 확실히 혼란 스러울 것입니다. 클래스 키워드가 없으면 이해하기 쉽습니다. 결국 기능 키워드가 있지만 키워드는 다릅니다. 그러나 생성자는 어떻습니까? 실제로 JavaScript에는 유사한 생성자가 있지만 생성자라고합니다. 새 연산자를 사용할 때 생성자가 호출되었으며 이는 객체로 제작되었습니다. 예를 들어 다음 코드를 사용합니다
코드 사본은 다음과 같습니다.
var 동물 = 동물 ( "Wangwang");
동물은 정의되지 않습니다. 어떤 사람들은 물론 반환 가치가 정의되지 않았다고 말할 것입니다. 그런 다음 동물 객체 정의를 변경하면 :
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
this.name = 이름;
이것을 반환하십시오;
}
지금 어떤 동물이 무엇인지 맞춰보세요?
현재 동물은 창문이되었습니다. 차이점은 창이 확장되어 창에 이름 속성이 있다는 것입니다. 이 기본값은 창에 대한 기본값, 즉 최상위 변수를 지정하지 않고하기 때문입니다. 새 키워드를 호출 하여만 생성자를 올바르게 호출 할 수 있습니다. 그렇다면 새로운 키워드를 사용하는 사람이 놓친 새로운 키워드를 피하는 방법은 무엇입니까? 약간의 변경을 할 수 있습니다.
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
if (! (이 인스턴스 동물)) {
새로운 동물 (이름)을 반환합니다.
}
this.name = 이름;
}
이것은 완벽 할 것입니다. 생성자는 또한 인스턴스가 속한 객체를 나타내는 다른 용도를 가지고 있습니다. 우리는 인스턴스를 사용하여 판단 할 수 있지만 인스턴스는 상속 할 때 조상 객체와 실제 물체로 진실을 반환하므로 그다지 적합하지 않습니다. 생성자를 새로 호출하면 기본적으로 현재 객체를 가리 킵니다.
코드 사본은 다음과 같습니다.
Console.log (Animal.prototype.constructor === 동물); // 진실
우리는 다르게 생각할 수 있습니다 : 프로토 타입은 함수의 시작 부분에서 가치가 없으며 구현은 다음 논리 일 수 있습니다.
// set __proto__는 함수의 내장 멤버입니다. get_prototyoe ()는 메소드입니다.
코드 사본은 다음과 같습니다.
var __proto__ = null;
함수 get_prototype () {
if (! __ proto__) {
__proto__ = new Object ();
__proto __. 생성자 = 이것;
}
__proto__;
}
이 이점은 선언 된 모든 함수에 대한 객체 인스턴스를 생성하지 않고 오버 헤드를 저장한다는 것입니다. 생성자를 수정할 수 있으며 나중에 논의됩니다. 모든 사람들이 프로토 타입을 기반으로 한 상속이 무엇인지 알고 있으므로 IQ 하한을 과시하지 않습니다.
몇 가지 유형의 JS 상속이 있습니다. 여기에 두 가지 유형이 있습니다.
방법 1이 방법은 가장 일반적으로 사용되며 안전성이 향상됩니다. 먼저 두 객체를 먼저 정의해 봅시다
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
this.name = 이름;
}
기능 개 (Age) {
this.age = age;
}
var dog = 새로운 개 (2);
상속을 구성하는 것은 매우 간단합니다. 자식 대상의 프로토 타입을 부모 객체의 인스턴스에 가리키는 것은 매우 간단합니다 (객체가 아닌 인스턴스 임)
코드 사본은 다음과 같습니다.
dog.prototype = 새로운 동물 ( "Wangwang");
현재 개는 이름과 나이의 두 가지 속성을 가질 것입니다. 인스턴스 운영자가 개에 사용되는 경우
코드 사본은 다음과 같습니다.
Console.log (개 인스턴스 동물); // 진실
Console.log (개 인스턴스 개); // 거짓
이것은 상속을 달성하지만 작은 문제가 있습니다
코드 사본은 다음과 같습니다.
console.log (dog.prototype.constructor === 동물); // 진실
console.log (dog.prototype.constructor === dog); // 거짓
생성자가 가리키는 물체가 변경되었으며, 이는 우리의 목적을 충족시키지 못하는 것을 알 수 있습니다. 우리는 우리가 새로운 인스턴스에 속하는 사람을 결정할 수 없습니다. 따라서 하나의 문장을 추가 할 수 있습니다.
코드 사본은 다음과 같습니다.
dog.prototype.constructor = 개;
다시 한번 봅시다 :
코드 사본은 다음과 같습니다.
Console.log (개 인스턴스 동물); // 거짓
Console.log (개 인스턴스 개); // 진실
완료. 이 방법은 프로토 타입 체인의 유지 보수의 링크이며, 아래에서 자세히 설명합니다. 2. 방법 2이 방법에는 그 이점과 단점이 있지만 단점은 그 이점을 능가합니다. 코드를 먼저보십시오
코드 사본은 다음과 같습니다.
<pre name = "code"> function Animal (name) {
this.name = 이름;
}
Animal.prototype.setname = function (name) {
this.name = 이름;
}
기능 개 (Age) {
this.age = age;
}
dog.prototype = Animal.Prototype;
이를 통해 프로토 타입을 복사 할 수 있습니다.
이 방법의 장점은 객체의 인스턴스화가 필요하지 않다는 것입니다 (방법 1과 비교)는 자원을 저장합니다. 단점도 분명합니다. 위와 동일한 문제, 즉 생성자가 상위 물체를 가리키며, 부모 객체가 프로토 타입으로 선언 한 속성과 방법 만 복사 할 수 있습니다. 즉, 위 코드에서 동물 객체의 이름 속성을 복사 할 수는 없지만 SetName 메소드를 복사 할 수 있습니다. 가장 치명적인 것은 Child Object의 프로토 타입에 대한 모든 수정이 부모 객체의 프로토 타입에 영향을 미친다는 것입니다. 즉, 두 개체가 선언 한 인스턴스는 영향을받습니다. 따라서이 방법은 권장되지 않습니다.
프로토 타입 체인
상속에 관해 글을 쓴 사람은 상속 재산이 여러 수준에서 상속 될 수 있다는 것을 알고 있습니다. 그리고 JS에서 이것은 프로토 타입 체인을 형성합니다. 위의 기사는 프로토 타입 체인을 여러 번 언급 했으므로 프로토 타입 체인은 무엇입니까? 인스턴스에는 최소한 프로토 속성이 프로토 타입을 가리키는 것이 있어야하며, 이는 JavaScript의 객체 시스템의 기초입니다. 그러나이 속성은 보이지 않으며, 우리는 그것을 "내부 프로토 타입 체인"이라고 부르며,이를 생성자의 프로토 타입으로 구성된 "생성자 프로토 타입 체인"과 구별합니다 (즉, 일반적으로 "프로토 타입 체인"이라고합니다. 위의 코드에 따라 간단한 상속 관계를 구축하겠습니다.
코드 사본은 다음과 같습니다.
기능 동물 (이름) {
this.name = 이름;
}
기능 개 (Age) {
this.age = age;
}
var 동물 = 새로운 동물 ( "Wangwang");
dog.prototype = 동물;
var dog = 새로운 개 (2);
앞에서 언급 한 바와 같이, 모든 객체는 빈 개체를 상속합니다. 그래서 우리는 프로토 타입 체인을 구성합니다.
우리는 Child Object의 프로토 타입이 Parent Object의 인스턴스를 가리키고 생성자 프로토 타입 체인을 형성한다는 것을 알 수 있습니다. Child 인스턴스의 내부 프로토 객체는 또한 내부 프로토 타입 체인을 형성하는 부모 객체를 가리키는 인스턴스입니다. 속성을 찾아야 할 때 코드는
코드 사본은 다음과 같습니다.
함수 getAttrfromobj (attr, obj) {
if (typeof (obj) === "개체") {
var proto = obj;
while (proto) {
if (proto.hasownproperty (attr)) {
리턴 프로토 [attr];
}
proto = proto .__ proto__;
}
}
정의되지 않은 반환;
}
이 예에서는 개에서 이름 속성을 찾으면 개의 멤버 목록에서 검색됩니다. 물론, 개의 멤버 목록은 항목 연령이기 때문에 발견되지 않을 것입니다. 그런 다음 프로토 타입 체인을 따라 계속 검색 할 것입니다. 검색이 존재하지 않는 속성 인 경우 동물에서 찾을 수없는 경우 .Proto로 계속 검색하고 빈 객체를 찾은 다음 .proto로 계속 검색하고 빈 객체의 .proto가 널을 찾아 출구를 찾습니다.
프로토 타입 체인의 유지 보수 지금 우리는 프로토 타입 상속에 대해 이야기했을 때 의문을 제기했습니다. 상속을 구성하기 위해 방법 10을 사용하는 경우, Child Object 인스턴스의 생성자는 부모 객체를 가리 킵니다. 이것의 장점은 생성자 속성을 통해 프로토 타입 체인에 액세스 할 수 있다는 것입니다. 단점도 분명합니다. 인스턴스가 스스로를 가리키는 객체, 즉
코드 사본은 다음과 같습니다.
(new obj ()). 프로토 타입 .constructor === obj;
그런 다음 프로토 타입 특성을 다시 작성한 후, Child Object에 의해 생성 된 인스턴스의 생성자는 자체를 가리키지 않습니다! 이것은 생성자의 원래 의도에 위배됩니다. 위의 해결책을 언급했습니다.
코드 사본은 다음과 같습니다.
dog.prototype = 새로운 동물 ( "Wangwang");
dog.prototype.constructor = 개;
아무것도 잘못된 것 같습니다. 그러나 실제로, 이것은 우리가 부모 객체를 찾을 수 없기 때문에 프로토 타입 체인으로 돌아갈 수 없다는 것을 알게 될 것이기 때문에 새로운 문제가 발생합니다. 따라서 SpiderMonkey는 개선 사항을 제공합니다. 생성자가 사용하는 프로토 타입을 가리키는 생성 된 개체에 __proto__라는 속성을 추가하십시오. 이러한 방식으로, 생성자의 모든 수정은 __proto__의 값에 영향을 미치지 않으므로 생성자를 유지하는 것이 편리합니다.
그러나 두 가지 문제가 더 있습니다.
__proto__는 다시 작성 가능합니다. 즉, 사용할 때 여전히 위험이 있음을 의미합니다.
__proto__는 SpiderMonkey의 특별한 처리이며 다른 엔진 (예 : JSCRIP)에서는 사용할 수 없습니다.
프로토 타입의 생성자 특성을 유지하고 서브 클래스 생성자 함수 내에서 인스턴스의 생성자 특성을 초기화하는 또 다른 방법이 있습니다.
코드는 다음과 같습니다. Child Object를 다시 작성하십시오
코드 사본은 다음과 같습니다.
기능 개 (Age) {
this.constructor = arguments.callee;
this.age = age;
}
dog.prototype = 새로운 동물 ( "Wangwang");
이러한 방식으로, 모든 자식 객체 인스턴스의 생성자는 객체를 올바르게 가리키고, 프로토 타입의 생성자는 부모 객체를 가리 킵니다. 이 방법은 인스턴스를 구성 할 때마다 생성자 속성을 다시 작성해야하기 때문에 비교적 비효율적이지만,이 방법이 이전 모순을 효과적으로 해결할 수 있다는 것은 의심의 여지가 없습니다. ES5는 이것을 고려 하고이 문제를 완전히 해결합니다. 객체. getPrototype ()는 언제든지 생성자에 액세스하거나 외부 프로토 타입 체인을 유지하지 않고 객체의 실제 프로토 타입을 얻는 데 언제든지 사용될 수 있습니다. 따라서 이전 섹션에서 언급했듯이 다음과 같이 다시 작성할 수 있습니다.
코드 사본은 다음과 같습니다.
함수 getAttrfromobj (attr, obj) {
if (typeof (obj) === "개체") {
하다 {
var proto = object.getPrototypof (dog);
if (proto [attr]) {
리턴 프로토 [attr];
}
}
(프로토);
}
정의되지 않은 반환;
}
물론이 방법은 ES5를 지원하는 브라우저에서만 사용할 수 있습니다. 후진 호환성이 되려면 여전히 이전 방법을 고려해야합니다. 보다 적합한 방법은이 두 가지 방법을 통합하고 캡슐화하는 것입니다. 나는 독자들이 이것에 매우 능숙하다고 생각하기 때문에 여기서 추악하지 않을 것입니다.