本文通過對《JavaScript高級程序設計》第六章的理解,加上自己的理解,重組了部分內容,形成下面的文字。
理解了原型這個概念,你的JS世界會清明很多。
為什么要為JS創造原型這個概念
在沒有原型概念之前,我們可以通過創建各種形式的函數來模擬類,但總有這樣那樣的不足,比如下面的
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.sayName===person2.sayName; // true
其中的sayName()函數雖然可以被構造函數Person的多個實例共享,但無法被其他構造函數繼承。
什么是原型
要想弄清楚原型鏈,需要先理解原型。
所謂原型,是其他OO語言中類和繼承的JS自家解決方案。JS以引用類型數據特性為基礎,通過為構造函數自動創建prototype對象屬性,實現類和繼承,這個自動創建的prototype就是原型,也叫原型對象。所以要想搞清楚原型這個概念,首先要掌握引用類型、類、繼承這幾個概念。JS不像其他編程語言,如Java,類、構造方法、方法等概念之間有明確的界限,它用於模仿類和繼承的原型對象本質是引用類型數據(Object),同樣的,構造方法和成員方法也是。原型是JS引入的一個概念,用於解決實現類和繼承,接下來看看是原型這個概念如何引入的。
無論什么時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在默認情況下,所有原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype 屬性所在函數的指針。就拿下面的例子來說,Person.prototype. constructor 指向Person。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
如上代碼涉及構造函數(Person)、原型對象(Person.prototype)和實例對象等三個概念。創建了自定義的構造函數之后,其原型對象默認只會取得constructor 屬性;至於其他方法,則都是從Object 繼承而來的。當調用構造函數創建一個新實例后,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。ECMA-262 第5 版中管這個指針叫[[Prototype]]。雖然在腳本中沒有標准的方式訪問[[Prototype]],但Firefox、Safari 和Chrome 在每個對象上都支持一個屬性__proto__;而在其他實現中,這個屬性對腳本則是完全不可見的。不過,要明確的真正重要的一點就是,這個連接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。如下圖:
什么是原型鏈
前面說,JS引入原型來更好的模仿類的功能,而原型鏈則用於實現繼承。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。簡單回顧一下構造函數、原型和實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等於另一個類型的實例,結果會怎么樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
如上,SubType.prototype = new SuperType(),實現繼承的本質是用被繼承構造函數(類)的實例重寫原型對象。所有引用類型默認都繼承了Object,而這個繼承也是通過原型鏈實現的。大家要記住,所有函數的默認原型都是Object 的實例,因此默認原型都會包含一個內部指針,指向Object.prototype。這也正是所有自定義類型都會繼承toString()、valueOf()等默認方法的根本原因。下圖為我們展示了該例子中完整的原型鏈: