摘要:繼承與拷貝本文討論中如何實現(xiàn)繼承關(guān)系,以及如何拷貝對象。談完繼承方法后,再談?wù)搶ο蟮目截?。我們在臨時構(gòu)造器法基礎(chǔ)上進(jìn)一步完善之。這讓我想到了中覆蓋構(gòu)造函數(shù)的辦法,如下關(guān)于繼承的話題到此結(jié)束。
JavaScript 繼承與拷貝
Date: 7th of Aug, 2015
Author: HaoyCn
本文討論JavaScript中如何實現(xiàn)繼承關(guān)系,以及如何拷貝對象。下面,我們分別探討4種繼承方法。談完繼承方法后,再談?wù)搶ο蟮目截悺?/p>
需要提前說明的是,后文需要繼承的構(gòu)造器函數(shù)是:
var Animal = function(name){ this.name = name; }; Animal.prototype.jump = function(){ console.log("jumped"); };原型鏈繼承法
本方法的特性在于,能夠把可以重用的部分遷移到原型鏈中,而不可重用的則設(shè)置為對象的自身屬性。
var Human = function(name){ this.name = name; }; // 這一步會使得 // Human.prototype.constructor = Animal; // 所以需要重新手動重定向 Human.prototype = new Animal; // 如果不更改 // 通過`Human`構(gòu)造器構(gòu)造出來的對象的構(gòu)造器會變成`Animal`而不是`Human` Human.prototype.constructor = Human; var man = new Human("HaoyCn"); man.jump();
現(xiàn)在,對象 man 可以使用 Animal.prototype.jump 方法,查找過程是:
man 自身沒有jump方法
查找 man.constructor.prototype,即Human.prototype,可Human.prototype本身也沒有 jump 方法,而它又是一個由 Animal 構(gòu)造的對象,所以
查找 Animal.prototype,在其中找到了 jump 方法,執(zhí)行之
僅從原型繼承法和“原型鏈繼承法”相比,本方法的優(yōu)點在于,提高了運行時的效率,沒有創(chuàng)建新對象出來。
var Human = function(name){ this.name = name; }; Human.prototype = Animal.prototype; var man = new Human("HaoyCn"); man.jump();
這時候的查找過程是:
man 自身沒有jump方法
查找 man.constructor.prototype,即Human.prototype,Human.prototype是對 Animal.prototype 的引用,在其中找到了 jump 方法,執(zhí)行之
減少了一步。然而代價則是:對 Human.prototype 的修改都會影響到 Animal.prototype,因為前者是對后者的引用。
一個致命缺點就是,無法修正子類構(gòu)造的對象的 constructor。
測試一下:
man.constructor === Animal;//true
我們來回顧一下 new 的過程:
var newProcess = function(){ var ret; // 構(gòu)造一個新對象 var obj = {}; // 構(gòu)造函數(shù) var Constructor = Array.prototype.shift.call(arguments); // 記錄原型 obj.__proto__ = Constructor.prototype; // 運用構(gòu)造函數(shù)給新對象設(shè)置屬性 ret = Constructor.apply(obj,arguments); // 始終返回一個對象 return "object" === typeof ret ? ret : obj; };
我們以此來回顧下“僅從原型繼承法”是如何創(chuàng)建出 man 的。
// var man = newProcess(Human,"HaoyCn"); // 還原如下 var ret; var man = {}; // var Constructor = Array.prototype.shift.call(arguments); // 即是 //var Constructor = Human; man.__proto__ = Human.prototype; // ret = Human.apply(obj,arguments); // `Human`構(gòu)造器執(zhí)行的是 man.name = "HaoyCn"; // `Human`構(gòu)造器返回的是 undefined,即 ret = undefined; // 所以最后`newProcess`返回`man`
因此,就不難理解了:
man.constructor === man.__proto__.constructor === Human.prototype.constructor === Animal.prototype.constructor === Animal臨時構(gòu)造器繼承法
“僅從原型繼承法”的問題暴露出來了:Animal.prototype 會因?qū)?Human.prototype 的修改而改變。如果被改變了,由 Animal 構(gòu)造出來的對象也會發(fā)生改變。我們來舉個例子:
var monkey = new Animal("monkey"); var woman = new Human("woman"); monkey.jump();// jumped woman.jump();// jumped // 下面的修改會影響`Animal.prototype` Human.prototype.jump = function(){ console.log("I refuse"); }; // 原本構(gòu)造好的對象也會被影響 monkey.jump();// I refuse woman.jump();// I refuse
那么,我們?nèi)绾我?guī)避這個問題呢?
“臨時構(gòu)造器繼承法”使用一個中介函數(shù),如下
var F = function(){}; F.prototype = Animal.prototype; var Human = function(name){ this.name = name; }; Human.prototype = new F; Human.prototype.constructor = Human; Human.prototype.sing = function(){ console.log("Mayday"); }; var man = new Human("HaoyCn"); man.jump(); man.sing();
我們對 Human.prototype 的任何改變都變成了對一個由中介構(gòu)造器創(chuàng)建的對象的屬性的修改。jump 查找過程是:
man 自身沒有jump方法
查找 man.constructor.prototype,即Human.prototype,可Human.prototype本身也沒有 jump 方法,而它又是一個由 F 構(gòu)造的對象,所以
查找 F.prototype,即 Animal.prototype,在其中找到了 jump 方法,執(zhí)行之
那這個方法同最開始的“僅從原型繼承法”相比,又有什么進(jìn)步呢?
先看“僅從原型繼承法”中的操作:
Human.prototype = new Animal; // 這將造成: // Human.prototype.name = undefined;// 沒有給`Animal`傳入?yún)?shù)之故
也就是說,Human.prototype 會多出不必要的屬性來,而中介器則避免了這種不必要的屬性。
構(gòu)造器借用法以上繼承法共通的一個缺點在于,Human 構(gòu)造器構(gòu)造的對象雖然可以共用 Animal.prototype,但對于 name 屬性而言,Human 構(gòu)造器只能自己再寫一遍構(gòu)造 name 屬性,為什么不把初始化屬性的方法也共(借)用呢?
構(gòu)造器借用法應(yīng)運而生?,F(xiàn)在我們把 name 屬性的創(chuàng)建還是交給 Animal,然后再為 Human 增加 country 屬性。我們在“臨時構(gòu)造器法”基礎(chǔ)上進(jìn)一步完善之。
var F = function(){}; F.prototype = Animal.prototype; var Human = function(){ Animal.apply(this,arguments); this.country = arguments[1]; } Human.prototype = new F; Human.prototype.constructor = Human; var man = new Human("HaoyCn","China"); console.log(man.country);// China
這樣,我們就輕輕松松地完成了偷懶。這讓我想到了PHP中覆蓋構(gòu)造函數(shù)的辦法,如下
// PHP class Human{ public $name; public $country; function __construct($name,$country){ parent::__construct($name); $this->country = $country; } }
關(guān)于繼承的話題到此結(jié)束。接下來談拷貝。
原型屬性拷貝法利用了原型機(jī)制。在高級瀏覽器中,有 Object.create 方法來完成對對象的拷貝,我們現(xiàn)在就簡單地還原之:
Object.create = Object.create || function(obj){ var F = function(){}; F.prototype = obj; return new F; }
可以看到,這是一種淺拷貝。如果我們對被拷貝對象進(jìn)行修改,也會影響到新對象。舉例如下:
var man = { name: "HaoyCn", jump: function(){ console.log("jumped"); } }; var monkey = Object.create(man); monkey.jump();// jumped man.jump = function(){ console.log("I refuse"); }; monkey.jump();// I refuse淺拷貝與深拷貝
問題擺在面前,如何深拷貝?
我們拷貝對象除了“原型屬性拷貝法”之外,還可以通過遍歷來完成。如淺拷貝遍歷:
var man = { name: "HaoyCn", jump: function(){ console.log("jumped"); } }; var monkey = {}; for(var i in man){ monkey[i] = man[i]; } monkey.jump();// jumped
而深拷貝要做的就是,如果屬性還是個對象,就遞歸拷貝。
function deepCopy(origin,copy){ copy = copy || {}; for(var i in origin){ if("object" === typeof origin[i]){ // 判斷是否為數(shù)組還有更好辦法,這里從簡 copy[i] = ("Array" === origin[i].constructor) ? [] : {}; deepCopy(origin[i],copy[i]); }else{ copy[i] = origin[i]; } } }
以上是深拷貝的一個扼要原理代碼。更復(fù)雜的檢驗過程,可以參考 jQuery.extend。但是,這樣的拷貝(包括jQuery.extend的深拷貝)只能完成對純粹對象的深拷貝,而函數(shù)、RegExp、Date等都無法深拷貝。
以上。關(guān)于對非純粹對象的深拷貝的方法我還在探索中,比如調(diào)用 toString() 后再構(gòu)造對象的方式,但都不夠完善,如果您在此方面有心得,敬請指教!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85857.html
摘要:中常常會看到這種代碼變量與的比較這種用法很有問題用來判斷變量是否被賦予了一個合理的值比如不好的寫法執(zhí)行一些邏輯這段代碼中方法顯然是希望是一個數(shù)組因為我們看到的擁有和這段代碼的意圖非常明顯如果參數(shù)不是一個數(shù)組則停止接下來的操作這種寫法的問題在 js中, 常常會看到這種代碼: 變量與null的比較(這種用法很有問題), 用來判斷變量是否被賦予了一個合理的值. 比如: const Contr...
摘要:閱讀小札一閱讀前自大學(xué)課上,就開始接觸設(shè)計模式,但對設(shè)計模式卻鮮有研究與實踐。第二部分是核心部分,由淺到深講解個設(shè)計模式。設(shè)計模式遵循的原則所有設(shè)計模式罪訓(xùn)的一條原則就是找出程序中變化的地方,并將變化封裝起來。 閱讀小札 · 閱讀前 自大學(xué)Java課上,就開始接觸設(shè)計模式,但對設(shè)計模式卻鮮有研究與實踐。最近向公司反映和游說技術(shù)提升,得以獲得公司提供購書機(jī)會,借此認(rèn)真學(xué)習(xí)前端學(xué)習(xí)之路的...
摘要:上一篇你不知道的筆記寫在前面這是年第一篇博客,回顧去年年初列的學(xué)習(xí)清單,發(fā)現(xiàn)僅有部分完成了。當(dāng)然,這并不影響年是向上的一年在新的城市穩(wěn)定連續(xù)堅持健身三個月早睡早起游戲時間大大縮減,學(xué)會生活。 上一篇:《你不知道的javascript》筆記_this 寫在前面 這是2019年第一篇博客,回顧去年年初列的學(xué)習(xí)清單,發(fā)現(xiàn)僅有部分完成了。當(dāng)然,這并不影響2018年是向上的一年:在新的城市穩(wěn)定、...
摘要:原文鏈接一什么是非構(gòu)造函數(shù)的繼承比如,現(xiàn)在有一個對象,叫做中國人。通過函數(shù),繼承了。中國北京上海香港廈門北京上海香港廈門北京上海香港這時,父對象就不會受到影響了。目前,庫使用的就是這種繼承方法。 原文鏈接 一、什么是非構(gòu)造函數(shù)的繼承? 比如,現(xiàn)在有一個對象,叫做中國人。 var Chinese = { nation: 中國 } 還有一個對象,叫做醫(yī)生。 var Doctor = {...
摘要:靜態(tài)屬性靜態(tài)方法目前支持靜態(tài)方法表示,類屬性及靜態(tài)屬性目前作為提案還未正式成為標(biāo)準(zhǔn)。在中,抽象類不能用來實例化對象,主要做為其它派生類的基類使用。不同于接口,抽象類可以包含成員的實現(xiàn)細(xì)節(jié)。中也是這樣規(guī)定的抽象類不允許直接被實例化。 嘗試重寫 在此之前,通過《JavaScript => TypeScript 入門》已經(jīng)掌握了類型聲明的寫法。原以為憑著那一條無往不利的規(guī)則,就可以開開心心的...
閱讀 1713·2021-11-24 09:39
閱讀 2497·2021-11-18 10:07
閱讀 3678·2021-08-31 09:40
閱讀 3351·2019-08-30 15:44
閱讀 2642·2019-08-30 12:50
閱讀 3665·2019-08-26 17:04
閱讀 1441·2019-08-26 13:49
閱讀 1279·2019-08-23 18:05