首頁 >web前端 >js教程 >JavaScript原型與繼承實例詳解

JavaScript原型與繼承實例詳解

零下一度
零下一度原創
2017-06-26 10:55:541027瀏覽

原型鏈

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物件會影響所有的實例。

「子類別」繼承實作的幾種方式

之所以子類別要加引號,是因為這裡說「類別」的概念是不嚴謹的。 JavaScript是一門物件導向的語言,但它跟Java等語言不同,在ES6標準出爐之前,它是沒有類別的定義的。

但熟悉Java等語言的程式設計師,也希望使用JavaScript時,跟使用Java相似,透過類別產生實例,透過子類別重複使用程式碼。那麼在ES6之前,怎麼做到像如下程式碼一樣使用類似"類"的方式呢?

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~~"
這第一次改進還不如不改。

第二次改進-暫時建構函式/

Object.create()

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

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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn