摘要:此時的原型對象包括一個指向另一個原型的指針,相應的,另一個原型中的指向另一個構造函數(shù)。這種關系層層遞進,就通過一個原型對象鏈接另一個構造函數(shù)的原型對象的方式實現(xiàn)了繼承。
讀這篇之前,最好是已讀過我前面的關于對象的理解和封裝類的筆記。
第6章我一共寫了3篇總結,下面是相關鏈接:
讀《javaScript高級程序設計-第6章》之理解對象
讀《javaScript高級程序設計-第6章》之封裝類
原型鏈最簡單的理解就是:原型對象指向另一個構造函數(shù)的實例。此時的原型對象包括一個指向另一個原型的指針,相應的,另一個原型中的constructor指向另一個構造函數(shù)。這種關系層層遞進,就通過一個原型對象鏈接另一個構造函數(shù)的原型對象的方式實現(xiàn)了繼承。
下面用代碼和圖來詳細分析一下原型鏈中的各種關系:
function SuperType(){ ??? this.property = true; } SuperType.prototype.getSuperValue = function(){ ??? return this.property; }; function SubType(){ ??? this.subproperty = false; } //inherit from SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ ??? return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue());?? //true alert(instance.getSubValue());?? //false alert(instance instanceof Object);????? //true alert(instance instanceof SuperType);?? //true alert(instance instanceof SubType);???? //true alert(Object.prototype.isPrototypeOf(instance));??? //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance));?? //true console.log(new SuperType()); console.log(instance);
下圖是上面代碼中打印出來的new SuperType()和instance的分析:
從上面的分析我們看到的原型鏈:
SubType的原型里有指向SuperType的原型的指針,SuperType的原型里有指向Object的原型的指針。
也可以看紅皮書里的圖:
訪問屬性的搜索過程:
當以讀取模式訪問一個構造函數(shù)(SubType)的實例的屬性時,首先會在實例中搜索實例屬性。如果沒找到該屬性,則會繼續(xù)搜索實例的原型;SubType繼承了SuperType,那么實例的原型是另一個構造函數(shù)(SuperType)的實例,搜索實例的原型也就是在SuperType的實例中搜索該屬性,沒找到繼續(xù)搜索SuperType的原型;SuperType繼承了Object,以此遞進,一層層搜索,直到找到或者搜到了原型鏈的末端停下來。
判斷原型和實例的關系
(1)instanceof
實例的原型鏈中出現(xiàn)過待檢測的構造函數(shù),就會返回true
alert(instance instanceof Object);????? //true alert(instance instanceof SuperType);?? //true alert(instance instanceof SubType);???? //true
(2)isPrototypeOf()方法
待檢測對象出現(xiàn)在instance的原型鏈中,就會返回true
alert(Object.prototype.isPrototypeOf(instance));??? //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance));?? //true
注意事項
(1)給原型添加方法的代碼一定要放在替換原型的語句之后。也就是
SubType.prototype = new SuperType();這句代碼一定要先寫,在寫下面的代碼 //new method SubType.prototype.getSubValue = function (){ ??? return this.subproperty; }; //override existing method SubType.prototype.getSuperValue = function (){ ??? return false; };
(2)在通過原型鏈實現(xiàn)繼承時,不能使用對象字面量為原型添加屬性,因為這會重寫原型鏈(具體請看理解對象篇里的一、創(chuàng)建對象)。
如下:
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; ??? }, ??? someOtherMethod : function (){ ??????? return false; ??? } }; var instance = new SubType(); alert(instance.getSuperValue());?? //error!
其實這兩個注意事項,只要你明白了(理解對象篇里的一、創(chuàng)建對象)后,根本不需要解釋。
原型鏈的問題
(1)沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數(shù)傳遞參數(shù)。
(2)在另一篇筆記封裝類原型模式中提到過,原型中的屬性是被共享的,但如果屬性的值時引用類型,會有問題的。而在繼承時,原型實際上會是另一個類型的實例(這個實例包含引用類型值的實例屬性),那么原先的這個實例的實例屬性就會成為現(xiàn)在的原型屬性了,就會出現(xiàn)同樣的問題了。共享了引用類型值的屬性。
直接上代碼吧:
function SuperType(name){ ??? this.name = name; } function SubType(){? ??? //繼承了 SuperType ,同時還傳遞了參數(shù) ??? SuperType.call(this, "Nicholas"); ??? ??? //實例屬性 ??? this.age = 29; } var instance = new SubType(); alert(instance.name);??? //"Nicholas"; alert(instance.age);???? //29
如上寫法就解決了原型鏈里的兩個問題了,為什么呢?請看下面的講解:
SuperType,如果你用new調(diào)用它是構造函數(shù),但你不用new,它就是個普通函數(shù)。SuperType.call(this, "Nicholas");不但傳遞了參數(shù),還綁定了子類的作用域,就相當于SuperType方法在幫助定義子類的實例屬性。也就是說,即使SuperType的中定義的屬性里有引用類型值,也不會成為子類SubType的原型屬性,仍然時實例屬性。我們要時刻記住實例屬性是每個實例所私有的,而原型屬性是會被所有實例所共享的。
當然這也寫也不完美,問題顯而易見,和構造函數(shù)模式同樣的問題。
三、組合繼承組合繼承,就像是封裝類里的把構造函數(shù)模式和原型模式組合使用是一樣的。這里是把原型鏈和借用構造函數(shù)相組合。
簡單來說就是:使用原型鏈實現(xiàn)對原型屬性和方法的繼承,通過借用構造函數(shù)實現(xiàn)對實例屬性的繼承(父類的實例屬性變成子類的實例屬性)。
還是上代碼吧:
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.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
解釋:
下圖是instance1的打印
我們可以看到instance1具有了父類SuperType的實例屬性name 、colors,但是子類的原型是父類的實例,所以原型中仍存在父類的實例屬性,但是子類已經(jīng)有了同樣的實例屬性name和colors,所以子類原型中的這兩個屬性就被屏蔽了。從子類訪問它的name和colors屬性只會訪問到它的實例屬性。
組合繼承是javaScript中最常用的繼承模式。而且instance和isPrototypeOf()也能夠用于識別給予組合繼承創(chuàng)建的對象類型。
四、原型式繼承感興趣可以了解一下。
原型鏈中,我們是讓原型對象指向一個構造函數(shù)的實例,這個實例本質上就是一個對象。原型式繼承就是讓原型對象指向一個已有的對象,不必創(chuàng)建自定義類型。如下:
function object(o){ ??? function F(){} ??? F.prototype = o; ??? return new F(); } var person = { ??? name: "Nicholas", ??? friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends);?? //"Shelby,Court,Van,Rob,Barbie”
大家還記得原型模式嗎。我的理解:這就是一個原型模式,區(qū)別是object這個方法就相當于一個工廠,你傳給它一個對象,它就給你一個原型是這個對象的實例。這個實例就會相應的繼承到了你傳給它的那個對象的屬性。
當然你也可以不用自己寫上面的object這個方法,因為ES5提供了,而且更規(guī)范。ES5中新增了Object.create()方法規(guī)范化了原型式繼承。這個方法接受兩個參數(shù):一個是用做新對象原型的對象和(可選)一個為新對象定義額外屬性的對象(或者說是定義新對象的實例屬性的對象,這個參數(shù)和defineProperties()方法的第二個參數(shù)格式相同:每個屬性都是通過自己的描述符定義的)。
上代碼:
var person = { ??? name: "Nicholas", ??? friends: ["Shelby", "Court", "Van"] }; ?????????????????? var anotherPerson = Object.create(person, { ??? name: { ??????? value: "Greg" ??? } }); console.log(anotherPerson);
打印結果圖:
從上圖可以看到第二個參數(shù)定義的name屬性是新對象的實例屬性,它會屏蔽掉它的原型屬性里的同名屬性name。簡單來說,Object.create就是用原型模式創(chuàng)建新對象的一個工廠,第一個參數(shù)定義了原型屬性,第二個參數(shù)定義了實例屬性。
五、寄生式繼承這一小節(jié),感興趣了解一下。
六、寄生組合式繼承前面說過,組合繼承是js里最常用的繼承模式,但是它并不完美。問題是:調(diào)用了兩次超類SuperType的構造函數(shù),子類創(chuàng)建了一部分多余的屬性(這部分屬性是超類的實例屬性,在子類的實例屬性里存在并有用,但在子類的原型中也存在且沒用)。寄生組合式繼承就是解決這個問題的。
上代碼:
function object(o){ ??? function F(){} ??? F.prototype = o; ??? return new F(); } function inheritPrototype(subType, superType){ ??? var prototype = object(superType.prototype);?? //create object??? prototype.constructor = subType;?????????//augment object??? subType.prototype = prototype;???????? //assign object } ??????????????????????? 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; } inheritPrototype(SubType, SuperType); 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 console.log(instance1); console.log(SuperType.prototype)
代碼運行打印結果圖:
從圖中可以看到instance1(子類實例)的原型里已經(jīng)沒有了超類的實例屬性name、colors。而且代碼中只運行了一次超類構造函數(shù)。怎么做到的呢?請看下面的解釋:
我們先看這段代碼:
function inheritPrototype(subType, superType){ ??? var prototype = object(superType.prototype);?? //create object ??? prototype.constructor = subType;?????????????? //augment object ??? subType.prototype = prototype;???????????????? //assign object }
subType的原型還是指向了一個對象,這個對象是什么呢?object這個方法返回的對象,這個對象是一個構造函數(shù)是空的,原型指向超類原型的實例。什么意思呢?就是說subType的原型還是一個構造函數(shù)的實例,但不是超類SuperType的實例,而是一個新建的臨時的空的構造函數(shù)F的實例。看代碼:
function object(o){ ??? function F(){} ??? F.prototype = o; ??? return new F(); }
這個臨時的構造函數(shù)F具有和超類SuperType一樣的原型。那么這個時候的子類的原型中就只有F的實例屬性和原型,而F的實例屬性是空的,就只有F的原型,F(xiàn)的原型就是超類SuperType的原型。這樣子類的實例屬性還是繼承了超類的實例屬性,而子類的原型屬性只繼承了超類的原型。完美,就這樣。
啰嗦一句我對面向對象程序設計的理解,面向對象程序設計就是一直在說如何使用對象。其實,只要結果符合你的預期,對象真的是想怎么使用就怎么使用,不一定非得像書中說的什么各種模式的。當然書中的這么多種模式方法的介紹可以了解一下(但是構造函數(shù)模式、原型模式。以及繼承里的原型鏈、借用構造函數(shù)。還包括它們的組合使用還是需要認真研讀,深刻理解的。再順便說一句,繼承里的原型鏈、借用構造函數(shù)可以看作是原型模式和構造函數(shù)模式的進化),可以加深自己對對象的理解,有助于你花式使用對象的方法。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/89309.html
摘要:把對象定義為無序屬性的集合,其屬性可以包含基本值對象或函數(shù)。接受兩個參數(shù)屬性所在的對象和要讀取其特性的屬性名返回的時其特性的對象例如讀高級程序設計這本書的第章面向對象的程序設計,我做了篇筆記。這是第一篇,后面還有兩篇,分別是封裝類和繼承。 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或函數(shù)。所以,我們可以理解對象就是名值對的集合,名就是對象的每個屬性的名字,...
摘要:創(chuàng)建構造函數(shù)后,其原型對象默認只會取得屬性至于其他的方法都是從繼承來的。上圖展示了構造函數(shù)的原型對象和現(xiàn)有的兩個實例之間的關系。所有原生的引用類型都在其構造函數(shù)的原型上定義了方法。 第6章我一共寫了3篇總結,下面是相關鏈接:讀《javaScript高級程序設計-第6章》之理解對象讀《javaScript高級程序設計-第6章》之繼承 工廠模式 所謂的工廠模式就是,把創(chuàng)建具體對象的過程抽象...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環(huán)境的關閉是頁面關閉或者瀏覽器關閉,而局部環(huán)境的關閉是指函數(shù)結束。數(shù)值范圍最大和最小的范圍是超出范圍的數(shù)字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內(nèi)容好像...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環(huán)境的關閉是頁面關閉或者瀏覽器關閉,而局部環(huán)境的關閉是指函數(shù)結束。數(shù)值范圍最大和最小的范圍是超出范圍的數(shù)字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內(nèi)容好像...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環(huán)境的關閉是頁面關閉或者瀏覽器關閉,而局部環(huán)境的關閉是指函數(shù)結束。數(shù)值范圍最大和最小的范圍是超出范圍的數(shù)字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內(nèi)容好像...
閱讀 2868·2021-11-19 09:40
閱讀 3726·2021-11-15 18:10
閱讀 3309·2021-11-11 16:55
閱讀 1276·2021-09-28 09:36
閱讀 1682·2021-09-22 15:52
閱讀 3392·2019-08-30 14:06
閱讀 1187·2019-08-29 13:29
閱讀 2333·2019-08-26 17:04