진정한 의미에서 JavaScript는 객체 지향 언어가 아니며 전통적인 상속 방법을 제공하지 않지만 상속을 달성하기 위해 제공하는 프로토 타입 속성을 사용하여 프로토 타입 상속 방법을 제공합니다.
프로토 타입 및 프로토 타입 체인
프로토 타입 상속에 대해 이야기하기 전에 프로토 타입과 프로토 타입 체인에 대해 먼저 이야기해야합니다. 결국, 이것은 프로토 타입 상속을 실현하기위한 기초입니다.
JavaScript에는 각 기능에는 자체 프로토 타입을 가리키는 프로토 타입 속성 프로토 타입이 있으며이 기능에 의해 생성 된 객체에는이 프로토 타입을 가리키는 __proto__ 속성도 있습니다. 함수의 프로토 타입은 객체 이므로이 객체는 자체 프로토 타입을 가리키는 __proto__가 있으므로 객체 객체의 프로토 타입이 될 때까지 레이어에 의해 더 깊은 레이어가되어 프로토 타입 체인을 형성합니다. 다음 그림은 자바 스크립트의 프로토 타입과 프로토 타입 체인 간의 관계를 잘 설명합니다.
각 함수는 함수 함수에 의해 생성 된 객체이므로 각 함수에는 함수 기능의 프로토 타입을 가리키는 __proto__ 속성이 있습니다. 프로토 타입 체인을 실제로 형성하는 것은 기능의 프로토 타입 속성이 아니라 각 객체의 __proto__ 속성이라는 점을 지적해야합니다.
프로토 타입 상속
기본 모드
코드 사본은 다음과 같습니다.
var parent = function () {
this.name = '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function () {
this.name = 'child';
};
child.prototype = new Parent ();
var parent = new Parent ();
var child = new Child ();
console.log (parent.getName ()); //조상
console.log (child.getName ()); //어린이
이것은 프로토 타입 상속을 구현하는 가장 쉬운 방법이며, 서브 클래스 생성자의 프로토 타입에 부모 클래스의 객체를 직접 할당하여 서브 클래스의 객체가 부모 클래스의 속성과 부모 클래스 생성자의 프로토 타입에 액세스 할 수 있도록합니다. 이 방법의 프로토 타입 상속 다이어그램은 다음과 같습니다.
이 방법의 장점은 분명합니다. 구현은 매우 간단하며 특별한 운영이 필요하지 않습니다. 단점도 분명합니다. 서브 클래스가 상위 클래스 생성자와 동일한 초기화 조치를 수행 해야하는 경우 서브 클래스 생성자의 상위 클래스에서 작업을 반복해야합니다.
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
this.name = name || '어린이' ;
};
child.prototype = new Parent ();
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
위의 상황에서는 이름 속성 만 초기화되면됩니다. 초기화 작업이 계속 증가하면이 방법은 매우 불편합니다. 따라서 다음을 개선하는 방법이 있습니다.
생성자를 빌리십시오
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
parent.apply (이것, 인수);
};
child.prototype = new Parent ();
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
위의 방법은 서브 클래스 생성자의 상위 클래스 생성자에 호출을 적용하여 동일한 초기화 작업을 수행하므로, 부모 클래스에서 초기화 작업이 얼마나 많은지에 관계없이 서브 클래스는 동일한 초기화 작업을 수행 할 수 있습니다. 그러나 위 구현에는 또 다른 문제가 있습니다. 부모 클래스 생성자는 서브 클래스 생성자에서 한 번 두 번 실행되었으며 서브 클래스 프로토 타입에서는 많은 중복이므로 개선해야합니다.
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
parent.apply (이것, 인수);
};
child.prototype = parent.prototype;
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
이런 식으로, 우리는 서브 클래스 생성자에서 부모 클래스 생성자를 한 번만 실행하면되며 동시에 부모 클래스 프로토 타입에서 속성을 상속받을 수 있습니다. 이는 프로토 타입의 원래 의도와 더 일치하며, 이는 프로토 타입에 재사용 해야하는 컨텐츠를 넣는 것입니다. 우리는 프로토 타입에서 재사용 가능한 컨텐츠 만 상속합니다. 위의 방법의 프로토 타입 다이어그램은 다음과 같습니다.
임시 생성자 모드 (성배 모드)
위의 생성자 패턴을 빌린 버전에는 여전히 문제가 있습니다. 부모 클래스의 프로토 타입을 서브 클래스의 프로토 타입에 직접 할당하는데, 이는 문제를 일으킬 것입니다. 즉, 서브 클래스의 프로토 타입이 수정되면 수정은 부모 클래스의 프로토 타입에도 영향을 미치고 부모 클래스 객체에 영향을 미칩니다. 이것은 분명히 모든 사람이보고 싶어하는 것이 아닙니다. 이 문제를 해결하기 위해 임시 생성자 패턴을 사용할 수 있습니다.
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
parent.apply (이것, 인수);
};
var f = new function () {};
f. prototype = parent.prototype;
child.prototype = new f ();
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
이 방법의 프로토 타입 상속 다이어그램은 다음과 같습니다.
부모 클래스 프로토 타입과 아동 클래스 프로토 타입 사이에 임시 생성자 F를 추가함으로써 아동 클래스 프로토 타입과 부모 클래스 프로토 타입 간의 연결이 차단되어 아동 클래스 프로토 타입이 수정 될 때 부모 클래스 프로토 타입이 영향을받지 않도록 쉽게 알 수 있습니다.
내 방법
성배 모드는 "JavaScript 모드"로 끝나지 만 위의 방법에 관계없이 발견하기 쉽지 않은 문제가 있습니다. '부모'의 프로토 타입 속성에 OBJ 객체 문자 그럴 속성을 추가 한 것을 알 수 있지만 결코 유용하지 않았습니다. 성배 모드를 기반으로 다음 상황을 살펴 보겠습니다.
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
parent.apply (이것, 인수);
};
var f = new function () {};
f. prototype = parent.prototype;
child.prototype = new f ();
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
Console.log (child.obj.a); // 1
Console.log (parent.obj.a); // 1
child.obj.a = 2;
Console.log (child.obj.a); // 2
Console.log (parent.obj.a); // 2
위의 경우, 자식 객체 OBJ.A를 수정하면 부모 클래스 프로토 타입의 OBJ.A도 수정되므로 공유 프로토 타입과 동일한 문제가 발생합니다. 이는 child.obj.a에 액세스 할 때 프로토 타입 체인을 따르고 부모 클래스의 프로토 타입을 찾은 다음 OBJ 속성을 찾은 다음 OBJ.A를 수정하기 때문에 발생합니다. 다음 상황을 살펴 보겠습니다.
코드 사본은 다음과 같습니다.
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : 1};
var child = function (name) {
parent.apply (이것, 인수);
};
var f = new function () {};
f. prototype = parent.prototype;
child.prototype = new f ();
var parent = new Parent ( 'myparent');
var child = new Child ( 'mychild');
Console.log (child.obj.a); // 1
Console.log (parent.obj.a); // 1
child.obj.a = 2;
Console.log (child.obj.a); // 2
Console.log (parent.obj.a); // 2
여기에는 중요한 문제가 있습니다. 객체가 프로토 타입에서 속성에 액세스 할 때 프로토 타입의 특성은 객체와 읽기 전용입니다. 즉, Child Object는 OBJ 객체를 읽을 수 있지만 프로토 타입의 OBJ 객체 참조를 수정할 수는 없습니다. 따라서 자식이 OBJ를 수정하면 프로토 타입의 OBJ에 영향을 미치지 않습니다. 그것은 단지 자체 객체에 OBJ 속성을 추가하여 부모 프로토 타입에서 OBJ 속성을 덮어 씁니다. Child Object가 OBJ.A를 수정하면 먼저 프로토 타입에서 OBJ에 대한 참조를 읽습니다. 현재, child.obj와 parent.prototype.obj는 동일한 대상을 가리므로 자식의 obj.a는 parent.prototype.obj.a의 값에 영향을 미치므로 부모 클래스의 대상에 영향을 미칩니다. AngularJS에서 $ 스코프 중첩의 상속 방법은 Javasript의 프로토 타입 상속을 모델링하여 구현됩니다.
위의 설명에 따르면, 서브 클래스 객체에 액세스 된 프로토 타입이 상위 클래스 프로토 타입과 동일하다면 위의 상황이 발생합니다. 따라서 부모 클래스 프로토 타입을 복사 한 다음 서브 클래스 프로토 타입에 할당 할 수 있습니다. 이러한 방식으로, 서브 클래스가 프로토 타입의 특성을 수정하면 부모 클래스 프로토 타입의 사본 만 수정하고 부모 클래스 프로토 타입에 영향을 미치지 않습니다. 특정 구현은 다음과 같습니다.
코드 사본은 다음과 같습니다.
var deepclone = function (source, target) {
소스 = 소스 || {};
var tostr = object.prototype.tostring,
arrstr = '[객체 배열]';
for (소스의 var i) {
if (source.hasownproperty (i)) {
var item = source [i];
if (typeof item === 'Object') {
대상 [i] = (tostr.apply (item) .tolowercase () === arrstr) : []? {};
딥 클론 (항목, 대상 [I]);
}또 다른{
딥 클론 (항목, 대상 [I]);
}
}
}
반환 대상;
};
var parent = function (name) {
this.name = name || '부모';
};
parent.prototype.getName = function () {
이 this.name;
};
parent.prototype.obj = {a : '1'};
var child = function (name) {
parent.apply (이것, 인수);
};
child.prototype = DeepClone (parent.prototype);
var child = new Child ( 'Child');
var parent = new Parent ( '부모');
Console.log (child.obj.a); // 1
Console.log (parent.obj.a); // 1
child.obj.a = '2';
Console.log (child.obj.a); // 2
Console.log (parent.obj.a); // 1
위의 모든 고려 사항을 기반으로, JavaScript 상속의 특정 구현은 다음과 같습니다. 자녀와 부모가 기능을 고려할 때만 다음과 같습니다.
코드 사본은 다음과 같습니다.
var deepclone = function (source, target) {
소스 = 소스 || {};
var tostr = object.prototype.tostring,
arrstr = '[객체 배열]';
for (소스의 var i) {
if (source.hasownproperty (i)) {
var item = source [i];
if (typeof item === 'Object') {
대상 [i] = (tostr.apply (item) .tolowercase () === arrstr) : []? {};
딥 클론 (항목, 대상 [I]);
}또 다른{
딥 클론 (항목, 대상 [I]);
}
}
}
반환 대상;
};
var extend = function (부모, 자식) {
자녀 = 자녀 || 기능(){} ;
if (parent === 정의되지 않은)
귀환 아이;
// 부모 클래스 생성자를 빌리십시오
child = function () {
부모.
};
// 딥 카피를 통해 부모 클래스 프로토 타입을 상속합니다
child.prototype = DeepClone (parent.prototype);
// 생성자 속성을 재설정합니다
child.prototype.constructor = child;
};
요약
실제로 JavaScript에서 상속을 구현하는 것은 매우 유연하고 다양하며 최상의 방법은 없습니다. 다른 요구에 따라 다른 상속 방법을 구현해야합니다. 가장 중요한 것은 JavaScript에서 상속을 구현하는 원리, 즉 프로토 타입 및 프로토 타입 체인의 문제를 이해하는 것입니다. 이것을 이해하는 한 직접 상속을 쉽게 구현할 수 있습니다.