在探討 javaScript 的原型繼承之前,先不妨想想為什麼要繼承?
考慮一個場景,如果我們有兩個對象,它們一部分屬性相同,另一個屬性不同。通常一個好的設計方案是將相同邏輯抽出來,實現重複使用。
以 xiaoMing liLei 兩位同學舉例。這兩位同學有自己的名字,會介紹自己。抽象化為程式對象,可以做如下表示。
var xiaoMing = { name : "xiaoMing", hello : function(){ console.log( 'Hello, my name is '+ this.name + '.'); } } var liLei = { name : "liLei", hello : function(){ console.log( 'Hello, my name is '+ this.name + '.'); } }
使用過 java 的同學,可能第一眼就想到了用物件導向來解決這個問題。創造一個 Person 的類,然後實例化 xiaoMing 和 liLei 兩個物件。在 ES6 中也有類似 java 中類別的概念: class 。
下面使用 ES6 的語法,用物件導向的想法來重構上面的程式碼。
class Person { constructor(name){ this.name = name } hello(){ console.log(this.name); } } var xiaoMing = new Person('xiaoMing'); var liLei = new Person('liLei');
可以看到,使用類別建立對象,達到了重複使用的目的。它基於的邏輯是,兩個或多個物件的結構功能類似,可以抽像出一個模板,並依照模板複製出多個相似的物件。
使用類別創建對象,就像自行車製造商一遍一遍地重複使用相同的藍圖來製造大量的自行車。
然解決重用問題的方案,當然不只一種。傳統物件導向的類,只是其中的一種方案。下面輪到我們的主角「原型繼承」登場了,它從另一個角度解決了重用的問題。
javaScript 中的 object 由兩部分組成,普通屬性的集合,和原型屬性。
var o = { a : 'a', ... __proto__: prototypeObj }
普通屬性指的是 a ; 原型屬性 指的是 __proto__ 。這本來不屬於規範的一部分,後來 chrome 透過 __proto__ 將這個語言底層屬性給暴露出來了,慢慢的被大家所接受,也就加入到 ES6 規範中了。 o.__proto__ 的值 prototypeObj 也就是 原型物件 。原型對像其實也就是一個普通對象,之所以叫原型對象的原因,只是因為它是原型屬性所指的值。
原型物件所以特殊,是因為它擁有一個普通物件沒有的能力:將它的屬性共享給其他物件。
在ES6 規範中,對它是如下定義的:
object that provides shared properties for other objects
回到最開始的例子,看看如何利用原型繼承實現重用的目的。
var prototypeObj = { hello: function(){ console.log( 'Hello, my name is '+ this.name + '.'); } // ... } var xiaoMing = { name : "xiaoMing", __proto__ : prototypeObj } var liLei = { name : "liLei", __proto__ : prototypeObj } xiaoMing.hello(); // Hello, my name is xiaoMing. liLei.hello(); // Hello, my name is liLei.
xiaoMing liLei 物件上,並沒有直接擁有 hello 屬性(方法),但是卻能讀取該屬性(執行該方法),這是為什麼?
想像一個場景,你在做數學作業,遇到一個很難的題目,你不會做。而你有一個好兄弟,數學很厲害,你去請教他,把這題做出來了。
xiaoMing 物件上,沒有 hello 屬性,但是它有一個好兄弟, prototypeObj 。屬性讀操作,在 xiaoMing 身上沒有找到 hello 屬性,就會去問它的兄弟 prototypeObj 。所以 hello 方法會被執行。
還是做數學題的例子。你的數學題目很難,你的兄弟也沒有答案,他推薦你去問另外一個同學。這樣直到有了答案或再也沒有人可以問,你就不會再問下去。這樣就好像有一條無形鏈條把你和同學們牽在了一起。
在 JS 中,讀取操作透過 __proto__ 會一層一層連結的結構,就叫 原型鏈 。
var deepPrototypeObj = { hello: function(){ console.log( 'Hello, my name is '+ this.name + '.'); } __proto__ : null } var prototypeObj = { __proto__ : deepPrototypeObj } var xiaoMing = { name : "xiaoMing", __proto__ : prototypeObj } var liLei = { name : "liLei", __proto__ : prototypeObj } xiaoMing.hello(); // Hello, my name is xiaoMing. liLei.hello(); // Hello, my name is liLei.
在上面的例子中,透過直接修改了 __proto__ 屬性值,實現了原型繼承。但在實際生產中,
取代的方式是使用 Object.create() 方法。
呼叫 Object.create() 方法會建立一個新對象,同時指定該對象的原型對象為傳入的第一個參數。
我們將上面的範例改一下。
var prototypeObj = { hello: function(){ console.log( 'Hello, my name is '+ this.name + '.'); } // ... } var xiaoMing = Object.create(prototypeObj); var liLei = Object.create(prototypeObj); xiaoMing.name = "xiaoMing"; liLei.name = "liLei"; xiaoMing.hello(); // Hello, my name is xiaoMing. liLei.hello(); // Hello, my name is liLei.
You-Dont-Know-JS 的作者,對這種原型繼承的實現取了一個很好玩的名字OLOO (objects-linked-to-other-objects) ,這種實現方式的優點是沒有使用任何類別的概念,只有object ,所以它是很符合javaScript 的特性的。
因為JS 中本無類,只有 object 。
無奈的是,喜歡類別的程式設計師是太多,所以在 ES6 新增了 class 概念。下一篇會講 class 在 JS 中的實作原理
類別建立對象,達到了重用的目的。它所基於的邏輯是,兩個或多個物件的結構功能類似,可以抽像出一個模板,依照模板 複製 出多個相似的物件。就像自行車製造商一再重複使用相同的藍圖來製造大量的自行車。
使用原型繼承,同樣可以達到重用的目的。它基於的邏輯是,兩個或多個物件的物件有一部分共用屬性,可以將共用的屬性抽像到另一個獨立公共物件上,透過特殊的原型屬性,將公共物件和普通物件連結起來,再利用屬性讀(寫)規則進行遍歷查找,實作屬性共用 。
以上就是深入理解 javaScript 原型繼承的內容,更多相關內容請關注PHP中文網(www.php.cn)!