JavaScript中的每個對象,都內建一個_proto_屬性,這個屬性是程式設計不可見的,它其實是對另一個物件或null的引用。
當物件參考一個屬性時,JavaScript引擎會先從此物件本身屬性表中查找,如果找到則進行對應讀寫操作,若沒有在自身的屬性表中找到,則在_proto_
屬性所引用的物件的性表中查找,如此往復,直到找到這個屬性或##_proto_ 屬性指向null
為止。
這個#_proto_#的引用鏈,被稱為原型鏈。
注意,這裡有一個效能最佳化的問題:往原型鏈越深處搜索,耗費的時間越多。
原型鍊與建構子JavaScript是一種物件導向的語言,可以進行原型繼承。
JavaScript中的函數有一個屬性prototype,這個prototype
屬性是一個對象,它的一個屬性constructor
引用該函數本身。即:
func.prototype.constructor === func; // ==> true這個屬性有什麼用呢?我們知道一個,一個函數使用
new操作符呼叫時,會作為建構函數傳回一個新的物件。
這個物件的_proto_屬性引用其建構子的prototype
屬性。
var obj = new Func(); obj.constructor == Func; // ==> true還有這個:
obj instanceof Func; // ==> true也是透過尋找原型鏈上的
constructor屬性實現的。
_proto_屬性是對同一個prototype
#物件的參考。所以修改prototype
物件會影響所有的實例。
var parent = new Parent("Sam");var child = new Children("Samson"); parent.say(); // ==> "Hello, Sam!"child.say(); // ==> "Hello, Samson! hoo~~"child instanceof Parent; // ==> true我們看到,這裡我們把建構子當作類別來用。 以下我們討論一下實作的幾個方式:最簡單的方式#結合原型鏈的概念,我們很容易就能寫出這樣的程式碼:
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~~"); }這個方式缺點很明顯:作為子類別的建構子需要依賴一個父類別的物件。這個物件中的屬性
name根本毫無用處。
// ...Children.prototype = Parent.prototype;// ...這樣就不會產生無用的父類別屬性了。 然而,這樣的話子類別和父類別的原型就引用了同一個對象,修改子類別的
prototype也會影響父類別的原型。
parent.say(); // ==> "Hello,Sam! hoo~~"這第一次改進還不如不改。 第二次改進-暫時建構函式/
function F(){ // empty } F.prototype = Parent.prototype; Children.prototype = new F();// ...parent.say(); // ==> "Hello, Sam!"child.say(); // ==> "Hello, Samson! hoo~~"這樣一來,修改子類別的原型只是修改了
F的一個
實例的屬性,並沒有改變Parent.prototype,從而解決了上面的問題。
ES5的時代,我們還可以直接這樣:
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万物诞生记。
以上是JavaScript原型與繼承實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!