javascript
【深入JavaScript】3.JavaScript继承的实现总结
一、JavaScript對象
ECMA-262把對象定義為:“無序屬性的集合,其屬性可以包含基本值、對象或者函數。”
說白了就是一組無序名值對,其中值可以是數據或函數。
?
二、創建對象的方法總結
1.創建Object的實例
(1)方式:創建一個Object類型的實例,然后再為它添加屬性和方法。
(2)例子:
var person = new Object(); person.name = "kwan"; person.age = 22; person.job = "student";person.sayName = function () {alert(this.name); };(3)優點:簡單,容易理解。
(4)缺點:使用同一個接口創建很多對象,會產生大量重復代碼,且無法識別對象類型。
(5)總結:這是創建自定義對象的最簡單的方式。
?
2.工廠模式
(1)方式:在JS中定義一個工廠函數來實現這一模式。
(2)例子:
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; }?
(3)優點:減少重復使用的代碼。
(4)缺點:無法識別對象的類型。
(5)總結:工廠模式抽象了創建具體對象的細節,工廠函數就像一個真正的工廠,只需為它傳遞參數,就可以流水線式產生相應的對象。
?
3.構造函數模式
(1)方式:創建自定義的構造函數,從而定義自定義對象類型的屬性和方法。
(2)例子:
function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.sayName = function () {alert(this.name);}; }(3)優點:可將它的實例標識為一種特定的類型。
(4)缺點:每個方法都要在每個實例上重新創建一遍,但其實方法是可以讓所有實例共享的。
而如果簡單地將函數定義轉移到外部,讓對象引用它,那么該模式又失去封裝性。
(5)總結:
1.構造函數模式中:
a.沒有顯式創建對象
b.直接將屬性和方法賦給了this對象
?? c.也沒有return語句
d.且函數名首字母大寫(慣例)
2.使用new操作符調用構造函數創建新實例的機制是:
a.創建一個新對象
b.將構造函數的作用域賦給新對象(因此this指向這個新對象)
c.執行構造函數中的代碼
d.返回新對象
3.新實例具有一個constructor屬性,該屬性指向構造函數。
4.構造函數與其他函數的唯一區別,就在于調用它們的方式不同。調用構造函數時需用new操作符。任何函數通過new操作符調用都會變成構造函數,而原本的構造函數若不使用new操作符而是直接調用,那和普通的函數沒什么兩樣,此時的this指代調用該函數的作用域。
4.原型模式
(1)方式:通過函數的prototype屬性,將可以共享的信息直接添加到原型對象中。
(2)例子:
function Person() {}Person.prototype.name = "kwan"; Person.prototype.age = "22"; Person.prototype.job = "student"; Person.prototype.sayName = function () {alert(this.name); };(3)優點:可讓所有對象實例共享其原型對象所包含的屬性和方法。
(4)缺點:首先,它省略了為構造函數傳遞初始化參數這一環節,結果所有實例在默認情況下都將取得相同的屬性值;
? 其次,由于其共享的本性,當屬性值為引用類型時,會導致很大問題。
(5)總結:
1.我們創建的每個函數都有一個prototype屬性,該屬性值為指針,指向一個對象,即原型對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。
2.使用原型模式創建的對象都在使用同一組屬性和函數,這便是該模式的共享特性。
3.若實例屬性與原型對象屬性同名,則優先取實例屬性值(屏蔽)
4.實例的內部屬性__proto__指向原型對象,這便是兩者之間的連接。繼承的實現,以及對象屬性的查找,其實都是依賴于這個連接。
? ? 5.讀取對象屬性的過程是:先找實例,后找原型。
6.原型是動態的,因此我們對原型對象所做的任何修改都能夠立即從實例上反映出來。
?
5.組合使用構造函數模式和原型模式(優化方案)
(1)方式:構造函數模式用于定義實例屬性,而原型模式用于定義方法和共享屬性。
(2)例子:
function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.friends = ["kwan", "soyi"]; }Person.prototype = {constructor : Person,sayName : function () {alert(this.name);} }(3)優點:每個實例都會有自己的一份實例屬性的副本,同時又共享對方法和共享屬性的引用,最大限度地節省了內存;另外,該模式支持向構造函數傳遞參數。
(4)缺點:封裝性不足。
(5)總結:挺好用。
?
6.動態原型模式
(1)方式:把所有信息都封裝在了構造函數中,通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。
(2)例子:
function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.friends = ["kwan", "soyi"];if (typeof this.sayName != "function") {//這個判斷是必要的,且只需寫一個條件Person.prototype.sayName = function () {alert(this.name);};} }(3)優點:僅在必要情況下通過在構造函數中初始化原型,保持同時使用構造函數和原型的優點,封裝度更高。
(4)缺點:暫無。
(5)總結:
通過if條件的判斷,只有在第一個實例被new出來的時候才會為原型對象添加屬性和方法,接下來的所有對象的實例化均不會調用添加操作,而且這樣寫可將所有信息封裝在構造函數中。
?
7.寄生構造函數模式(了解)
(1)方式:創建一個函數,該函數的作用僅僅是封裝創建對象的代碼,然后再返回新創建的對象。
(2)例子:
function Person(name, age, job) {var o = new Ojbect();o.name = name;o.age = age;o.job = job;o.sayName = function () {alert(this.name);};return o; }(3)優點:暫無。
(4)缺點:返回的對象與構造函數或構造函數的原型之間沒有關系,為此不能依賴instanceof操作符來確定對象類型。
(5)總結:
1.構造函數在不返回值的情況下,默認會返回新對象實例。而通過在構造函數的末尾添加一個return語句,可以重寫返回值。
2.這個模式的應用條件是:在特殊情況下用來為對象創建構造函數。盡量不要用這個模式。
?
8.穩妥構造函數模式(了解)
(1)方式:穩妥構造函數遵循與寄生構造函數類似的模式,但區別是:新創建對象的實例方法不引用this,并且不使用new操作符調用構造函數。
(2)例子:
function Person(name, age, job) {var o = new Ojbect();o.sayName = function () {alert(name);};return o; }(3)優點:暫無。
(4)缺點:返回的對象與構造函數或構造函數的原型之間沒有關系,為此不能依賴instanceof操作符來確定對象類型。
(5)總結:
1.所謂穩妥對象,指的是沒有公共屬性,而且其方法也不引用this的對象。
2.穩妥對象最適合在一些安全的環境中(這些環境中會禁止使用this和new),或者在防止數據被其他應用程序(如Mashup)改動時使用。
?
三、繼承總結
在ECMAScript中無法實現接口繼承,它只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。
1.原型鏈繼承
(1)什么是原型鏈?
原型鏈的基本思想是:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
我們讓原型對象A等于另一個類型的實例,此時的原型A將包含一個指向另一個原型B的指針,相應地,原型B中也包含著一個指向另一個構造函數的指針。假如原型B又是另一個原型C的實例,那么上述關系依然成立,如此層層推進,就構成了實例與原型的鏈條(A->B->C)。這就是原型鏈的概念。
(2)例子:
function SuperType(){ //父類this.property=true; }//在父類原型中添加方法 SuperType.prototype.getSuperValue=function(){return this.property; };function SubType(){ //子類this.subproperty=false; }//實現繼承的代碼: SubType.prototype=new SuperType();//在子類的新原型中添加方法 SubType.prototype.getSubValue=function(){return this.subproperty; };(3)原型鏈的本質是什么?
利用原型鏈實現繼承的本質是重寫原型對象,代之以一個新類型的實例。
同時,通過實現原型鏈,本質上擴展了原型搜索機制。
在通過原型鏈實現繼承后,搜索屬性或方法的過程就得以沿著原型鏈繼續向上。
(4)或許來一個圖,會更加清晰:
(5)原型鏈的問題:
1.當存在包含引用類型值的原型時,某個實例對引用類型值的修改,會反映在其余所有實例上;
2.在創建子類型的實例時,沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。
?
2.借用構造函數(偽造對象、經典繼承)
(1)基本思想:在子類型構造函數的內部調用超類型構造函數。
(2)例子:
function SuperType() { //父類this.colors = ["red", "blue", "green"]; }function SubType() {//繼承SuperTypeSuperType.call(this); }(3)優點:可在子類型構造函數中向超類型構造函數傳遞參數。
(4)缺點:僅使用此技術實現繼承,函數無法復用。而且在超類型的原型中定義的方法,對子類型而言也是不可見的。
(5)總結:
僅僅借用構造函數實現繼承,本質上是沒有利用到原型鏈的。因此很少單獨使用該模式。
?
3.組合繼承(偽經典繼承)
(1)方式:將原型鏈和借用構造函數的技術組合在一塊。
? 其基本思想是:使用原型鏈實現對原型屬性和方法的繼承,通過借用構造函數來實現對實例屬性的繼承。
(2)例子:
function SuperType(name) { //父類this.name = name;this.colors = ["red", "blue", "green"]; }SuperType.prototype.sayName = function () {alert(this.name); };function SubType(name, age) {//繼承SuperType的實例屬性SuperType.call(this, name); //第二次調用超類型構造函數this.age = age; }//繼承方法 SubType.prototype = new SuperType(); //第一次調用超類型構造函數 SubType.prototype.sayAge = function () {alert(this.age); };(3)優點:既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。
(4)缺點:無論在什么情況下,都會調用兩次超類型構造函數,以至于重寫超類型對象的實例屬性,造成空間的浪費。(這個自己畫圖或看書理解)
(5)總結:最常用的繼承模式。
?
4.寄生組合式繼承
(1)方式:通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
(2)基本思路:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型。
(3)例子:
首先,寄生組合式繼承的基本模式是:
function inheritPrototype(subType, superType) {var prototype = object(superType.prototype); //創建對象prototype.constructor = subType; //增強對象subType.prototype = prototype; //指定對象 }//原型式繼承 function object(o) {function F() {}F.prototype = o;return new F(); }示例中,object函數首先生成一個空白構造函數F,再將傳入的對象o指定為F的原型對象,最后返回一個F的實例。
這樣一來,返回的對象的原型就是o,而本身這個對象是空白的。
?
而inheritPrototype函數的作用是:
首先調用object函數,傳入超類型的原型,從而得到一個F的空白實例,此時該實例的原型就是超類型的原型;
然后,將這個實例的構造函數指針指定為subType,因此這個實例是用作subType的原型的;
最后,將第一步所得的空白實例對象指定為subType的原型。
至此,subType的原型不再是superType的實例,而是一個空白的實例,但這個實例原型又是suerType。
下面給出示例2:
function SuperType(name) { //父類this.name = name;this.colors = ["red", "blue", "green"]; }SuperType.prototype.sayName = function () {alert(this.name); };function SubType(name, age) {//繼承SuperType的實例屬性SuperType.call(this, name); //僅這一次調用超類型構造函數this.age = age; }inheritPrototype(SubType, SuperType);SubType.prototype.sayAge = function () {alert(this.age); };//繼承方法 SubType.prototype = new SuperType(); //第一次調用超類型構造函數 SubType.prototype.sayAge = function () {alert(this.age); };(4)優點:這個例子的高效率體現在它只調用一次SuperType的構造函數,并且因此避免了在SubType.prototype上創建不必要的、多余的屬性。與此同時,原型鏈還能保持不變。
?
5.原型式繼承(了解)
(1)方式:借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型。
(2)例子:
//原型式繼承 function object(o) {function F() {}F.prototype = o;return new F(); }(3)本質:object()對傳入其中的對象執行了一次淺復制。
(4)應用條件:這種繼承模式要求必須有一個對象可以作為另一個對象的基礎。如果有這么一個對象的話,可以把它傳遞給object()函數,然后再根據具體需求對得到的對象加以修改即可。
?
6.寄生式繼承(了解)
(1)思路:與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像是真的是它做了所有工作一樣返回對象。
(2)例子:
function createAnother(original) {var clone = object(original); //通過調用函數創建一個新對象clone.sayHi = function () {alert("hi");};//以某種方式來增強這個對象return clone; }?
四、總結
這一篇總結寫到我吐血...估計看的人也吐血了吧...已經盡可能簡單化了,希望各位喜歡。
共勉。
?
轉載于:https://www.cnblogs.com/stay-foolish/archive/2013/04/04/2482813.html
總結
以上是生活随笔為你收集整理的【深入JavaScript】3.JavaScript继承的实现总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Thrift简析
- 下一篇: SpringMVC 实例应用 -- 不同