理解原型對象:
不管何時,只要建立了新函數,就會根據一組特定的規則爲該函數建立一個 prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個constructor屬性,這個屬性包含一個指向prototype屬性的所在函數的指針。(重寫原型屬性會默認取消constructor屬性)詳細可見文章下圖。
建立object實例的方式有三種:對象字面量表示法、new操做符跟object構造函數、(ECMAScript5中的)object.create()函數。下面主要講的是最爲複雜的new操做符跟object構造函數的建立對象實例的方法。函數
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("NIcho",29,"software engineer");
工廠模式的問題
工廠模式雖然解決了建立多個類似對象的問題,可是沒有解決對象識別的的問題(即怎樣知道一個對象的類型)。測試
建立對象的自有屬性的主要方法,每定義一個函數就實例化了一個對象。this
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name); } } //將構造的函數Person實例化,傳入的參數做爲自有屬性,而且繼承自Person.prototype var person1=new Person(NIcho",29,"software engineer); var person2=new Person(Jhon",26,"software engineer);
Person函數代替了createPerson函數,還有如下不一樣之處:spa
咱們在這個例子中建立的對象既是object的實例又是person的實例(全部對象均繼承自object。object能夠理解爲對象的根原型,咱們所用到的全部對象方法的操做,如toString 、substring 、splice等都是繼承自object)。prototype
alert(person1 instanceOf Object);//true alert(person1 instanceOf Person);//true
構造函數模式的問題:指針
使用構造函數的主要問題是,每一個方法都要在每一個實例上建立一次。person1和person2都有一個sayName的方法,但兩個方法不是同一個Function實例(ECMAScript中的函數是對象,每定義一個函數就是實例化了一個對象)。
若是沒有自定義向person原型中添加屬性,實例化以後則獲得Person自有屬性的一個副本,而且繼承object(注:person1與person2中sayName()引用位置不一樣)。code
經過把函數定義轉移到構造函數外來解決:對象
//全局函數 function sayName(){ alert(this.name); }
對應的:繼承
this.sayName:sayName;
用於定義實例共享的屬性和方法。索引
function Person(){ } Person.prototype.name="Nicho"; Person.prototype.age=29; Person.prototype.jod="software engineer"; Person.prototype.sayName=function(){alert(this.name)}; var person1=new Person(); person1.sayName();//"Nicho" 繼承Person.prototype var person2=new Person(); person2.sayName();//"Nicho"繼承Person.prototype alert(person1.sayName==person2.sayName);//true 引用位置相同
(注:繼承鏈接存在與實例與構造函數的原型之間而不是實例與構造函數之間)
關於原型上的一些函數:
obj.isPrototypeOf(obj1)//返回true/false
object.getPrototypeOf(obj1)
Obj1.hasOwnProperty();//若是obj1有自有屬性則返回true,不然返回false
組合使用構造函數模式和原型模式既能夠繼承原型對象的屬性,又能夠設置自由屬性。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; } Person.prototype={ constructor:Person, sayName:function(){ alert("Hi"); } } var person1=new Person(「Nicho」,29,」software engineer」); //person1自有屬性:name aga job;原型屬性(來自繼承):constructor sayName
代碼讀取某個對象的某個屬性時的搜索方法
搜索首先從對象實例開始,若是實例中找到了給定名字的屬性,則返回該屬性的值。若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找給定名字的屬性。若是在原型對象中找到了該屬性,則返回屬性的值,不然一直向上查找給定名字的屬性,直到object。若是沒有找到則返回undefined。
這就須要咱們注意:雖然咱們能夠經過對象實例訪問原型中的值,但卻不能經過對象實例(注意!!這裏說的是經過對象實例而不是在原型中修改屬性)來重寫原型中的值。若是在實例中添加了一個與實例原型中同名的屬性,該屬性將會屏蔽原型中的那個屬性。
來看下面的的例子:
function Person(){ } Person.prototype.name="Nicho"; Person.prototype.age=29; Person.prototype.jod="software engineer"; Person.prototype.sayName=function(){alert(this.name)}; var person1=new Person(); var person2=new Person(); person1.name="Greg"; alert(person1.name);//Greg-來自實例 alert(person2.name);//Nicho-來自原型; //person1對屬性 name的修改並不能修改原型上的相應屬性,所以person2繼承自原型
person1=new Person()
與 person3=person1
是不一樣的:前者是實例化一個對象,後者遵循複製函數(對象引用)的原理var person3=person1;//整個對象的複製即引用相同(指針指針指針!只是一個索引,實際存儲都不在指針位置下) person3.name=」newName」;//修改屬性名稱的值 alert(person1.name);//newName;//另外一個來自此處的引用屬性的值會修改;即複製對象原理 alert(person1.isPrototypeOf(person3));//false alert(Person.prototype.isPrototypeOf(person3));//true
var person1=new Person("NIcho",29,"software engineer"); var person2=new Person("HIcho",9,"Hardware engineer"); person1.name=person2.name;//簡單的屬性的複製,並不是引用同一個屬性值 person2.name="NEW";//嘗試賦新值 alert(person1.name);//HIcho,說明是值的傳遞而並不是引用
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 getSuperValue來自superType.prototype alert(instance.property);//true property來自superType實例
實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原來存在於superType的實例的全部屬性和方法(包括自有屬性和繼承屬性),如今也存在於subType.prototype中了。
(此時instance.constructor如今指向的是superType,由於subType的原型指向了另外一個對象superType的原型,而這個原型對象的constructor指向的是superType)
注意:
alert(instance instanceOf superType);//true alert(instance instanceOf subType);//true
alert(superType.protoType.isPrototypeOf (instance));//true alert(subType.protoType.isPrototypeOf (instance));//true
原型鏈的問題:
在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了。
解決原型鏈繼承帶來的問題的方法
解決這個問題的技術是借用構造函數,使用最多的繼承模式是組合繼承。此外還有原型式繼承,寄生式繼承,寄生組合繼承。這裏主要講借用構造函數和組合繼承。
在子類型構造函數的內部調用超類型的構造函數。
function superType(){ this.colors=["red","blue","green"]; } function subType(){ //繼承了superType superType.call(this);//在新建立的subType實例的環境下調用superType函數 //這樣一來,就會在新subType對象上執行superType函數中定義的全部初始化代碼 //結果subType的每個實例都會具備本身的color屬性的副本了 } 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; this.colors = ["red","blue","green"]; } superType.prototype.sayName=function(){ console.log(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(){ console.log(this.age); } var instance1 = new subType("Nicho",29); instance1.sayName();//」Nicho」 instance1.sayAge();//29
總結:
this.age = age
superType.prptotype.sayName subtype.prototype.sayAge function superType(name){ this.name=name; this.colors=["red","blue","green"]; }
組合式繼承的問題
組合式繼承是js最經常使用的繼承模式,但組合繼承的超類型在使用過程當中會被調用兩次;一次是建立子類型的時候,另外一次是在子類型構造函數的內部。咱們使用Object.create
作一下改進:
function superType(name){ this.name = name; this.colors = ["red","blue","green"]; } superType.prototype.sayName=function(){ console.log(this.name); } function subType(name,age){ superType.call(this,name); this.age = age; } function inheritPrototype(sup,sub){ const obj = Object.create(sup); obj.constructor = sub; sub.prototype = obj; } // 繼承 inheritPrototype(superType,subType);
下面讓咱們看下一種狀況:重寫原型對象(徹底重寫而不是簡單的添加原型對象的屬性)
咱們知道,在調用構造函數是會爲實例添加一個prototype指針,而把這個原型修改成另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。請記住:實例中的指針僅指向原型而不指向構造函數。
function Person(){}; var friend=new Person(); Person.prototype={ Constructor:Person, Name:「Nicholas」, Age:29, sayName=function(){ alert(「this.name」); } } friend.sayName();//error
重寫原型對象以前:
重寫原型對象以後:
由圖可見:重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫,它們引用的還是最初的原型。