ECMAScript unterstützt nur die Implementierungsvererbung, und seine Implementierungsvererbung basiert hauptsächlich auf der Prototypenkette.
Prototypenkette
Die Grundidee der Prototypenkette besteht darin, mithilfe von Prototypen einem Referenztyp die Eigenschaften und Methoden eines anderen Referenztyps erben zu lassen. Jeder Konstruktor verfügt über ein Prototypobjekt, das Prototypobjekt enthält einen Zeiger auf den Konstruktor und die Instanz enthält einen Zeiger auf das Prototypobjekt. Wenn wir das Prototypobjekt A einer anderen Instanz vom Typ B gleichsetzen, dann hat das Prototypobjekt A einen Zeiger, der auf das Prototypobjekt von B zeigt, und das entsprechende Prototypobjekt von B speichert einen Zeiger auf seinen Konstruktor. Wenn das Prototypobjekt von B eine Instanz eines anderen Typs ist, gilt die obige Beziehung weiterhin, und so wird Schicht für Schicht eine Kette von Instanzen und Prototypen gebildet.
Das Beziehungsdiagramm zwischen Instanzen und Konstruktoren und Prototypen sieht wie folgt aus:
person.constructor zeigt jetzt auf den Parent. Dies liegt daran, dass Child.prototype auf den Prototyp des Parent verweist und der Konstruktor des Parent-Prototypobjekts auf den Parent zeigt.
Beim Zugriff auf eine Instanzeigenschaft im Lesemodus wird zunächst nach der Eigenschaft in der Instanz gesucht. Wenn die Eigenschaft nicht gefunden wird, wird die Suche nach dem Prototyp der Instanz fortgesetzt. Bei der Integration über eine Prototypenkette wird die Suche in der Kette fortgesetzt, bis das Ende der Kette erreicht ist.
Wenn Sie beispielsweise die Methode „person.getParentValue()“ aufrufen, 1) nach Instanzen suchen; 2) nach „Child.prototype“ suchen; 3) nach „Parent.prototype“ suchen;
1. Standardprototyp
Der im vorherigen Beispiel gezeigten Prototypenkette fehlt ein Link. Alle Referenztypen erben standardmäßig Objekt, und diese Vererbung wird auch über die Prototypenkette implementiert. Daher enthält der Standardprototyp einen internen Zeiger, der auf Object.prototype zeigt. Dies ist der grundlegende Grund, warum alle benutzerdefinierten Typen Standardmethoden wie toString() und ValueOf() erben. Mit anderen Worten: Object.prototype ist das Ende der Prototypenkette.
2. Bestimmen Sie die Beziehung zwischen Prototyp und Instanz
Die Beziehung zwischen dem Prototyp und der Instanz kann auf zwei Arten bestimmt werden: Die erste besteht darin, den Operator „instanceOf“ zu verwenden, und die zweite darin, die Methode „isPrototypeOf()“ zu verwenden.
Die Konstruktoren, die in der Prototypenkette „instanceOf“ erscheinen, geben alle „true“ zurück
console.log(person instanceOf Child);//true console.log(person instanceOf Parent);//true console.log(person instanceOf Object);//true isPrototype(),只要是原型链中出现过的原型,都可以说是该原型链所派生出来的实例的原型,因此也返回true. console.log(Object.prototype.isPrototypeOf(instance));//true console.log(Parent.prototype.isPrototypeOf(instance));//true console.log(Child.prototype.isPrototypeOf(instance));//true
3、谨慎地定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的莫个方法,注意:给原型添加方法的代码一定要放在替换原型的语句之后。
当通过Child的实例调用getParentValue()时,调用的是这个重新定义过的方法,但是通过Parent的实例调用getParentValue()时,调用的还是原来的方法。
格外需要注意的是:必须要在Parent的实例替换原型之后,再定义这两个方法。
还有一点需要特别注意的是:通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型链。
以上代码刚把Parent的实例赋值给Child的原型对象,紧接着又将原型替换成一个字面量,替换成字面量之后,Child原型实际上包含的是一个Object的实例,而不再是Parent的实例,因此我们设想中的原型链被切断.Parent和Child之间没有任何关联。
4、原型链的问题
原型链很强大,可以利用它来实现继承,但是也有一些问题,主要的问题还是包含引用类型值的原型属性会被所有实例共享。因此我们在构造函数中定义实例属性。但是在通过原型来实现继承时,原型对象其实变成了另一个类型的实例。于是原先定义在构造函数中的实例属性变成了原型属性了。
举例说明如下:
在Parent构造函数中定义了一个friends属性,该属性值是一个数组(引用类型值)。这样,Parent的每个实例都会各自包含自己的friends属性。当Child通过原型链继承了Parent之后,Child.prototype也用用了friends属性——这就好像friends属性是定义在Child.prototype一样。这样Child的所有实例都会共享这个friends属性,因此我们对kid1.friends做的修改,在kid2.friends中也会体现出来,显然,这不是我们想要的。
原型链的另一个问题是:在创建子类型的实例时,不能在不影响所有对象实例的情况下,给超类型的构造函数传递参数。因此,我们通常很少会单独使用原型链。
借用构造函数
为了解决原型中包含引用类型值带来的一些问题,引入了借用构造函数的技术。这种技术的基础思想是:在子类型构造函数的内部调用超类型构造函数。
Parent.call(this)在新创建的Child实例的环境下调用了Parent构造函数。在新创建的Child实例环境下调用Parent构造函数。这样,就在新的Child对象上,此处的kid1和kid2对象上执行Parent()函数中定义的对象初始化代码。这样,每个Child实例就都会具有自己的friends属性的副本了。
借用构造函数的方式可以在子类型的构造函数中向超类型构造函数传递参数。
为了确保子类型的熟悉不会被父类的构造函数重写,可以在调用父类构造函数之后,再添加子类型的属性。
构造函数的问题:
构造函数模式的问题,在于方法都在构造函数中定义,函数复用无从谈起,因此,借用构造函数的模式也很少单独使用。
组合继承
组合继承指的是将原型链和借用构造函数的技术组合在一块,从而发挥二者之长。即:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
Person构造函数定义了两个属性:name和friends。Person的原型定义了一个方法sayName()。Child构造函数在调用Parent构造函数时,传入了name参数,紧接着又定义了自己的属性age。然后将Person的实例赋值给Child的原型,然后又在该原型上定义了方法sayAge().这样,两个不同的Child实例既分别拥有自己的属性,包括引用类型的属性,又可以使用相同的方法了。
组合继承避免了原型链和构造函数的缺陷,融合了他们的有点,成为JavaScript中最常用的继承模式。而且,instanceOf和isPropertyOf()也能够识别基于组合继承创建的对象。
最后,关于JS对象和继承都还有几种模式没有写,或者说,我自己也还未去深刻研究,但是,我想,首先将组合模式应用的游刃有余。并且,对于为何选用组合模式,知其然,知其所以然。
关于JavaScript实现继承的几种方式(推荐),小编就给大家介绍到这里,希望对大家有所帮助!