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