面向對象的語言有一個標誌,那就是它們都有類的概念,而經過類能夠建立任意多個具備相同屬性和方法的對象編程
雖然Object構造函數或者對象字面量均可以用來建立單個對象,但這些方式有個明顯的缺點:使用同一個接口建立不少對象,會產生大量重複代碼(爲何或產生大量重複的代碼?)。app
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
工廠模式的特色:解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎麼知道一個對象的類型);函數
構造函數始終都應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。這個作法借鑑自其餘 OO 語言,主要是爲了區別於 ECMAScript中的其餘函數;由於構造函數自己也是函數,只不過能夠用來建立對象而已。性能
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
要建立 Person 的新實例,必須使用 new 操做符。這中方式調用構造函數實際上回會經歷4個步驟this
缺點: 每一個方法都要在每一個實例上從新建立一遍。spa
每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠有特定類型的全部實例共享的屬性和方法。prototype
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
新對象的這些屬性和方法是由全部實例共享的。換句話說,person1 和 person2 訪問的都是同一組屬性和同一個 sayName()函數指針
構造函數、原型和實例的關係:每一個構造函數都有一個原型對象(prototype屬性),原型對象都包含一個指向構造函數的指針(constructor(構造函數)屬性),而實例有包含一個指向原型對象的內部指針([[Prototype]]、Firefox、Safari 和 Chrome 在每一個對象上都支持一個屬性__proto__)code
function Person(){ } Person.prototype = { constructor : Person, // name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
本質上徹底重寫了默認的 prototype 對象,所以 constructor 屬性也就變成了新對象的 constructor 屬性(指向 Object 構造函數),再也不指向 Person 函數。因此須要手動設置 constructor 屬性,並將它的值設置爲 Person,確保經過該屬性可以訪問到適當的值。對象
原型模式的重要性不只體如今建立自定義類型方面,就連全部原生的引用類型,都是採用這種模式建立的。全部原生引用類型(Object、Array、String等)都在期構造函數的原型定義了方法。例如,在Array.prototype中能夠找到sort()方法,而在String.prototype中能夠找到substring()方法。
alert(typeof Array.prototype.sort); //"function" alert(typeof String.prototype.substring); //"function"
經過原生對象的原型,不只能夠取得全部默認方法的引用,並且也能夠定義新方法。能夠像修改自定義對象的原型同樣修改原型對象的原型,所以能夠隨時添加方法。下面的代碼就給基本包裝類型String添加了一個名爲 startsWith()的方法。
String.prototype.startsWith = function (text) { return this.indexOf(text) == 0; }; var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true
缺點:
最大問題是由其共享的本性所致使的。
原型中全部屬性是被不少實例共享的,這種共享對於函數很是合適。對於那些包含基本值的屬性倒也說的過去,畢竟(如前面的例子所示),經過在實例上添加同一個同名屬性,能夠隱藏在原型中的對應屬性。然而,對於包含引用類型值的屬性來講,問題就比較突出了。
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
基本思想:利用原型讓一個引用類型繼承另外一個類型的屬性和方法。
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
原型的搜索機制:當以讀取模式方式訪問一個實例屬性時,首先會在實例中搜索該屬性。若是沒有找到改屬性,則會繼續搜索實例的原型。在經過原型鏈實現繼承的狀況,搜索過程就得以沿着原型鏈繼續向上。在找不到屬性或方法的狀況下,搜索過程是要一環一環地前行到原型鏈末端纔會停下來。
咱們引用類型默認都繼承了Object,而這個繼承也是經過原型鏈實現的。你們要記住,全部函數的默認原型都是Object的實例,所以默認原型都會包含一個內部指針,指向Object.prototype。這也正式全部自定義類型都會繼承toString()、valueOf()等默認方法的根本緣由。
完整的原型鏈:
主要問題來自於包含引用類型的原型(引用類型值的原型屬性會被全部實例共享)。在經過原型來實現繼承時,原型實際上回變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //繼承了 SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
原型鏈的第二個問題是:在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。有鑑於此,在加上前面剛剛討論過的因爲原型中包含引用類型值所帶來的問題,實踐中不多會單獨使用原型鏈。
基本思想:在子類型構造函數的內部調用超類型構造函數。經過apply()和call()方法也能夠在(未來)新建立的對子那個上執行構造函數
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
傳遞參數.借用構造函數能夠在子類型構造函數中向超類型構造函數傳遞參數
function SuperType(name){ this.name = name; } function SubType(){ //繼承了 SuperType,同時還傳遞了參數 SuperType.call(this, "Nicholas"); //實例屬性 this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29
構造函數存在的問題——方法都在構造函數定義,所以函數就沒法複用。並且在超類型的原型中定義方法,對子類型而言也是不可見的,結果全部類型都只能在使用構造函數模式
組合繼承:有時候也叫僞經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。
基本思想: 使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,即經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //繼承屬性 SuperType.call(this, name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
instanceof 和 isPrototypeOf()也可以用於識別基於組合繼承建立的對象
ECMAScript 支持面向對象(OO)編程,但不使用類或者接口。對象能夠在代碼執行過程當中建立和加強,所以具備動態性而非嚴格定義的實體。在沒有類的狀況下,能夠採用下列模式建立對象。
JavaScript主要經過原型鏈實現繼承。原型鏈的構建是經過講一個類型的實例賦值給另外一個構造函數的原型實現的。這樣,子類型就可以訪問超類型的全部屬性和方法,這一點與基於類的繼承很類似。原型鏈的問題是對象實例共享全部繼承的屬性和方法,所以不適宜單獨使用。解決這個問題的技術是借用構造函數,即在子類型構造函數的內部調用超類型構造函數。這樣就能夠作到每一個實例都具備本身的屬性,同時還能保只使用構造函數模式來定義類型。使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而經過借用構造函數繼承實例屬性。
此外,還存在下列可供選擇的繼承模式。