Heim >Web-Frontend >js-Tutorial >Detaillierte Erläuterung des JavaScript-Prototyps und Vererbungsbeispiele
Jedes Objekt in JavaScript verfügt über ein integriertes _proto_. Dieses Attribut ist für die Programmierung nicht sichtbar. Ein Objekt oder ein Verweis auf null.
Wenn ein Objekt auf ein Attribut verweist, durchsucht die JavaScript-Engine es zunächst in der Attributtabelle des Objekts. Wenn es gefunden wird, führt es eine entsprechende Aktion aus Wenn es nicht in der eigenen Attributtabelle gefunden wird, suchen Sie in der Attributtabelle des Objekts, auf das durch das Attribut _proto_
verwiesen wird, und so weiter, bis das Attribut gefunden wird oder das _proto_
Attribut Punkte zu Bis null
.
Die Referenzkette von _proto_
wird Prototypenkette genannt.
Beachten Sie, dass hier ein Leistungsoptimierungsproblem vorliegt: Je tiefer Sie in der Prototypenkette suchen, desto länger dauert es.
JavaScript ist eine objektorientierte Sprache und kann prototypische Vererbung durchführen.
Die Funktion in JavaScript hat ein Attribut prototype
Dieses prototype
-Attribut ist ein Objekt und eines seiner Attribute ist constructor
Referenzieren Sie die Funktion selbst. Das heißt:
func.prototype.constructor === func; // ==> true
Wozu dient dieses Attribut? Wir wissen, dass eine Funktion, wenn sie mit dem Operator new
aufgerufen wird, ein neues Objekt als Konstruktor zurückgibt. Die _proto_
-Eigenschaft dieses Objekts verweist auf die prototype
-Eigenschaft seines Konstruktors.
Das ist also nicht schwer zu verstehen:
var obj = new Func(); obj.constructor == Func; // ==> true
Und dieses:
obj instanceof Func; // ==> true
auch übergeben Finden Sie die Implementierung der Eigenschaft constructor
in der Prototypenkette. Die
-Eigenschaften verschiedener Instanzen, die vom _proto_
-Konstruktor generiert werden, sind Verweise auf dasselbe prototype
-Objekt. Das Ändern des prototype
-Objekts wirkt sich also auf alle Instanzen aus.
Der Grund, warum Unterklassen in Anführungszeichen gesetzt werden müssen, liegt darin, dass das Konzept der „Klasse“ hier nicht streng ist. JavaScript ist eine objektorientierte Sprache, verfügt jedoch im Gegensatz zu Java und anderen Sprachen vor der Veröffentlichung des ES6-Standards über keine Klassendefinitionen.
Programmierer, die mit Sprachen wie Java vertraut sind, hoffen jedoch auch, dass sie bei der Verwendung von JavaScript Instanzen durch Klassen generieren und Code durch Unterklassen wiederverwenden können, ähnlich wie bei der Verwendung von Java. Wie kann man also vor ES6 eine „Klassen“-ähnliche Methode wie den folgenden Code verwenden?
var parent = new Parent("Sam");var child = new Children("Samson"); parent.say(); // ==> "Hello, Sam!"child.say(); // ==> "Hello, Samson! hoo~~"child instanceof Parent; // ==> true
Wir sehen, dass wir hier den Konstruktor als Klasse verwenden.
Lassen Sie uns verschiedene Möglichkeiten zur Implementierung besprechen:
In Kombination mit dem Konzept der Prototypenkette können wir problemlos Code wie diesen schreiben:
function Parent(name){this.name = name; } Parent.prototype.say = function(){ console.log("Hello, " + this.name + "!"); }function Children(name){this.name = name; } Children.prototype = new Parent(); Children.prototype.say = function(){ console.log("Hello, " + this.name + "! hoo~~"); }
Der Nachteil dieser Methode liegt auf der Hand: Als Konstruktor einer Unterklasse muss sie auf ein Objekt der übergeordneten Klasse angewiesen sein. Die Eigenschaften name
in diesem Objekt sind völlig nutzlos.
// ...Children.prototype = Parent.prototype;// ...
Damit keine nutzlosen übergeordneten Klassenattribute generiert werden.
In diesem Fall beziehen sich jedoch die Prototypen der Unterklasse und der übergeordneten Klasse auf dasselbe Objekt, und eine Änderung des prototype
der Unterklasse wirkt sich auch auf den Prototyp der übergeordneten Klasse aus.
Zu diesem Zeitpunkt stellten wir fest:
parent.say(); // ==> "Hello,Sam! hoo~~"
Diese erste Verbesserung ist schlimmer, als sich überhaupt nicht zu ändern.
function F(){ // empty } F.prototype = Parent.prototype; Children.prototype = new F();// ...parent.say(); // ==> "Hello, Sam!"child.say(); // ==> "Hello, Samson! hoo~~"
Ändern Sie auf diese Weise die Unterklasse. Der Prototyp ändert nur die Attribute einer F
-Instanz von und ändert sich nicht Parent.prototype
, wodurch das obige Problem gelöst wird.
Im Zeitalter von ES5 können wir dies auch direkt tun:
Children.prototype = Object.create(Parent.prototype);
这里的思路是一样的,都是让子类的prototype
不直接引用父类prototype
。目前的现代浏览器几乎已经添加了对这个方法的支持。(但我们下面会仍以临时构造函数为基础)
但是细细思考,这个方案仍有需要优化的地方。例如:如何让父类的构造函数逻辑直接运用到子类中,而不是再重新写一遍一样的?这个例子中只有一个name
属性的初始化,那么假设有很多属性且逻辑一样的话,岂不是没有做到代码重用?
使用apply/call
,实现“方法重用”的思想。
function Children(name){ Parent.apply(this, arguments);// do other initial things}
现在完整的代码如下:
function Parent(name){this.name = name; } Parent.prototype.say = function(){ console.log("Hello, " + this.name + "!"); }function Children(name){ Parent.apply(this, arguments);// do other initial things}function F(){ // empty } F.prototype = Parent.prototype; Child.prototype = new F(); Children.prototype.say = function(){ console.log("Hello, " + this.name + "! hoo~~"); }
这就是所谓“圣杯”模式,听着很高大上吧?
以上就是ES3的时代,我们用来实现原型继承的一个近似最佳实践。
“圣杯”模式依然存在一个问题:虽然父类和子类实例的继承的prototype
对象不是同一个实例,但是这两个prototype
对象上面的属性引用了同样的对象。
假设我们有:
Parent.prototype.a = { x: 1};// ...
那么即使是“圣杯”模式下,依然会有这样的问题:
parent.x // ==> 1child.x // ==> 1child.x = 2; parent.x // ==>2
问题在于,JavaScript的拷贝不是 深拷贝(deepclone)
要解决这个问题,我们可以利用属性递归遍历,自己实现一个深拷贝的方法。这个方法在这里我就不写了。
ES6极大的支持了工程化,它的标准让浏览器内部实现类和类的继承:
class Parent { constructor(name) { //构造函数 this.name = name; } say() { console.log("Hello, " + this.name + "!"); } } class Children extends Parent { constructor(name) { //构造函数super(name); //调用父类构造函数// ... } say() { console.log("Hello, " + this.name + "! hoo~~"); } }
从此走上强类型的不归路。。。
上张顿悟图
什么?还不明白?!麻烦出门左拐。推荐阮老师JavaScript万物诞生记。
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des JavaScript-Prototyps und Vererbungsbeispiele. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!