Javascript中類式繼承和原型式繼承有什么區(qū)別

這篇文章主要介紹了Javascript中類式繼承和原型式繼承有什么區(qū)別,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

成都創(chuàng)新互聯(lián)長期為1000+客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為巴宜企業(yè)提供專業(yè)的成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計巴宜網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

在所有面向?qū)ο蟮木幊讨校^承是一個重要的話題。一般說來,在設(shè)計類的時候,我們希望能減少重復(fù)性的代碼,并且盡量弱化對象間的耦合(讓一個類繼承另一個類可能會導(dǎo)致二者產(chǎn)生強耦合)。關(guān)于“解耦”是程序設(shè)計中另一個重要的話題,本篇重點來看看在javascript如何實現(xiàn)繼承。

其它的面向?qū)ο蟪绦蛟O(shè)計語言都是通過關(guān)鍵字來解決繼承的問題(比如extend或inherit等方式)。但是javascript中并沒有定義這種實現(xiàn)的機制,如果一個類需要繼承另一個類,這個繼承過程需要程序員自己通過編碼來實現(xiàn)。

一、類式繼承的實現(xiàn)

1、創(chuàng)建一個類的方式:

//定義類的構(gòu)造函數(shù)
function Person(name) {
  this.name = name || '默認(rèn)姓名';
}
//定義該類所有實例的公共方法
Person.prototype.getName = function() {
  return this.name;
}
var smith = new Person('Smith');
var jacky = new Person('Jacky');
console.log( smith.getName(), jacky.getName() ); //Smith Jacky

2、繼承這個類:

這需要分兩個步驟來實現(xiàn),第1步是繼承父類構(gòu)造函數(shù)中定義的屬性,第2步是繼承父類的prototype屬性

//定義類的構(gòu)造函數(shù)
function Person(name) {
  this.name = name || '默認(rèn)姓名';
}
//定義該類所有實例的公共方法
Person.prototype.getName = function() {
  return this.name;
}
function Author(name, books) {
  //繼承父類構(gòu)造函數(shù)中定義的屬性
  //通過改變父類構(gòu)造函數(shù)的執(zhí)行上下文來繼承
  Person.call(this, name);
  this.books = books;
}
//繼承父類對應(yīng)的方法
Author.prototype = new Person(); //Author.prototype.constructor === Person
Author.prototype.constructor = Author; //修正修改原型鏈時造成的constructor丟失
Author.prototype.getBooks = function() {
  return this.books;
};
//測試
var smith = new Person('Smith');
var jacky = new Author('Jacky', ['BookA', 'BookB']);
console.log(smith.getName()); //Smith
console.log(jacky.getName()); //Jacky
console.log(jacky.getBooks().join(', ')); //BookA, BookB
console.log(smith.getBooks().join(', ')); //Uncaught TypeError: smith.getBooks is not a function

從測試的結(jié)果中可以看出,Author正確繼承了Person,而且修改Author的原型時,并不會對Person產(chǎn)生影響。這其中的關(guān)鍵一句就是 Author.prototype = new Person(),要與Author.prototype = Person.prototype區(qū)分開來。前者產(chǎn)生了一個實例,這個實例有Person.prototype的副本(這里先這么理解,后面有更詳細(xì)的解析)。后者是指將兩者的prototype指向同一個原型對象。

那么,這也意味著每次繼承都將產(chǎn)生一個父類的副本,肯定對內(nèi)存產(chǎn)生消耗,但為了類式繼承這個內(nèi)存開銷必須得支付,但還可以做得更節(jié)省一點:Author.prototype = new Person()這一句其實多執(zhí)行了構(gòu)造函數(shù)一次(而這一次其實只需在子類構(gòu)造函數(shù)中執(zhí)行即可),尤其是在父類的構(gòu)造函數(shù)很龐大時很耗時和內(nèi)存。修改一下繼承的方式,如下:

Author.prototype = (function() {
  function F() {}
  F.prototype = Person.prototype;
  return new F();
})();

如上所示的代碼,new時,去掉了對父類的構(gòu)造函數(shù)的調(diào)用,節(jié)省了一次調(diào)用的開銷。

3、類式繼承顯著的特點是每一次實例化對象時,子類都將執(zhí)行一次父類的構(gòu)造函數(shù)。如果E繼承了D,D繼承了C,C繼承了B,B繼承了A,在實例化一個E時,一共要經(jīng)過幾次構(gòu)造函數(shù)的調(diào)用呢?

/*繼承方法的函數(shù)*/
function extend(son, father) {
  function F() {}
  F.prototype = father.prototype;
  son.prototype = new F();
  son.prototype.constructor = son;
}
//A類
function A() {
  console.log('A()');
}
A.prototype.hello = function() {
  console.log('Hello, world.');
}
//B類
function B() {
  A.call(this);
  console.log('B()');
}
extend(B, A);
//C類
function C() {
  B.call(this);
  console.log('C()');
}
extend(C, B);
//D類
function D() {
  C.call(this);
  console.log('D()');
}
extend(D, C);
//E類
function E() {
  D.call(this);
  console.log('E()');
}
extend(E, D);
//創(chuàng)建一個E的實例
var e = new E(); //A() B() C() D() E()
e.hello(); //hello, world.

5次,這還只是實例化一個E時調(diào)用的次數(shù)。所以,我們應(yīng)該盡可能的減少繼承的級別。但這并不是說不要使用這種類式繼承,而是應(yīng)該根據(jù)自己的應(yīng)用場合決定采用什么方法。

二、原型式繼承

1、先來看一段代碼:我們先將之前類式繼承中的繼承prototype那一段改成另一個函數(shù)clone,然后通過字面量創(chuàng)建一個Person,最后讓Author變成Person的克隆體。

//這個函數(shù)可以理解為克隆一個對象
function clone(object) {
  function F() {}
  F.prototype = object;
  return new F();
}
var Person = {
  name: 'Default Name';
  getName: function() {
    return this.name;
  }
}
//接下來讓Author變?yōu)镻erson的克隆體
var Author = clone(Person);

問一個問題:clone函數(shù)里的new F()為這個實例開辟內(nèi)存空間來存儲object的副本了嗎?

按我之前的理解,回答是肯定的。但是,當(dāng)我繼續(xù)將代碼寫下去的時候,奇怪的事情發(fā)生了,代碼如下:

//接下來讓Author變?yōu)镻erson的克隆體
var Author = clone(Person);
Author.books = [];
Author.getBooks = function() {
  return this.books.join(', ');
}
//增加一個作者Smith
var Smith = clone(Author);
console.log(Smith.getName(), Smith.getBooks()); //Default Name
Smith.name = 'Smith';
Smith.books.push('<<Book A>>', '<<Book B>>'); //作者寫了兩本書
console.log(Smith.getName(), Smith.getBooks()); //Smith <<Book A>>, <<Book B>>
//再增加一個作者Jacky
var Jacky = clone(Author);
console.log(Jacky.getName(), Jacky.getBooks()); // Default Name <<Book A>>, <<Book B>>

當(dāng)我們繼續(xù)增加作者Jacky時,奇怪的現(xiàn)象發(fā)生了??!Jacky的名字依然是Default Name,但是他居然也寫兩本與Smith一樣的書?Jacky的書都還沒push呢。到了這里,我想到了引用對象的情況(引用一個對象時,引用的是該對象的內(nèi)存地址),發(fā)生這樣的現(xiàn)象,問題肯定出在clone()函數(shù)中的new F()這里。

事實上,這個clone中的new F()確實返回了一個新的對象,該對象擁有被克隆對象的所有屬性。但這些屬性保留的是對被克隆對象中相應(yīng)屬性的引用,而非一個完全獨立的屬性副本。換句話說,新對象的屬性 與 被克隆的對象的屬性指向同一個內(nèi)存地址(學(xué)過C語言的同學(xué)應(yīng)該明白指針類型,這里意義差不多)。

那么為什么上面的代碼中,Jacky的書與Smith的書相同了,為什么Jacky的名字卻不是Smith而是Default Name呢?這就是Javascript中繼承的機制所在,當(dāng)Smith剛剛繼承自Author時,他的屬性保留了對Author的屬性的引用,一旦我們顯示的對Smith的屬性重新賦值時,Javascritp引擎就會給Smith的該屬性重新劃分內(nèi)存空間來存儲相應(yīng)的值,由于重新劃分了內(nèi)址地址,那么對Smith.name的改寫就不會影響到Author.name去了。這就很好的解釋了前面的那個問題——為什么Jacky的名字卻不是Smith而是Default Name。

2、基于原型繼承

通過前面的情況分析,可以看出基于原型繼承的方式更能節(jié)約內(nèi)存(只有在需要時候才開辟新的內(nèi)存空間)。但要注意:基于原型繼承時,對象的屬性一定要重新賦值后(重新劃分內(nèi)存)再去引用該屬性。對于對象的方法,如果有不同的處理方式,我們只需重新定義即可。

下面將前一段代碼做一個完整、正確的范例出來,以說明原型繼承的特點和使用方式:

//這個函數(shù)可以理解為克隆一個對象
function clone(object) {
  function F() {}
  F.prototype = object;
  return new F();
}
var Person = {
  name: 'Default Name',
  getName: function() {
    return this.name;
  }
}
//接下來讓Author變?yōu)镻erson的克隆體
var Author = clone(Person);
Author.books = [];
Author.getBooks = function() {
  return this.books.join(', ');
}
//增加一個作者Smith
var Smith = clone(Author);
Smith.name = 'Smith';
Smith.books = [];
Smith.books.push('<<Book A>>', '<<Book B>>'); //作者寫了兩本書
console.log(Smith.getName(), Smith.getBooks()); //Smith <<Book A>>, <<Book B>>
//再增加一個作者Jacky
var Jacky = clone(Author);
Jacky.name = 'Jacky';
Jacky.books = [];
Jacky.books.push('<<Book C>>', '<<Book D>>');
console.log(Jacky.getName(), Jacky.getBooks()); // Jacky <<Book C>>, <<Book D>>

三、類式繼承與原型式繼承的區(qū)別與相式之處

1、類式繼承中:使用構(gòu)造函數(shù)初始化對象的屬性,通過調(diào)用父類的構(gòu)造函數(shù)來繼承這些屬性。通過new 父類的prototype來繼承方法。

2、原型式繼承中:去掉了構(gòu)造函數(shù),但需要將對象的屬性和方法寫一個{}里申明。準(zhǔn)確的說,原型式繼承就是類式繼承中繼承父類的prototype方法。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Javascript中類式繼承和原型式繼承有什么區(qū)別”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

新聞標(biāo)題:Javascript中類式繼承和原型式繼承有什么區(qū)別
網(wǎng)站URL:http://muchs.cn/article10/pichgo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、外貿(mào)建站、品牌網(wǎng)站制作、、商城網(wǎng)站、營銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站