javascript
JavaScript大杂烩4 - 理解JavaScript对象的继承机制
JavaScript是單根的完全面向?qū)ο蟮恼Z(yǔ)言
JavaScript是單根的面向?qū)ο笳Z(yǔ)言,它只有單一的根Object,所有的其他對(duì)象都是直接或者間接的從Object對(duì)象繼承。而在JavaScript的眾多討論中,JavaScript的繼承機(jī)制也是最讓人津津樂(lè)道的,在了解它的機(jī)制之前,先讓我們溫習(xí)一下繼承的本質(zhì)。
?
繼承的本質(zhì)
繼承的本質(zhì)是重用,就是這么簡(jiǎn)單,重用是軟件編程技術(shù)向前發(fā)展最原始的動(dòng)力。從語(yǔ)法上來(lái)講,繼承就是"D是B"的描述,其中B是基類(lèi),描述共性,D是子類(lèi),描述特性。例如"貓是動(dòng)物",動(dòng)物就是基類(lèi),貓是子類(lèi),動(dòng)物的共性,比如會(huì)呼吸,貓也會(huì),但是貓的特性,如打呼嚕,別的動(dòng)物就不一定有了。
?
JavaScript的原型繼承機(jī)制
前面我們已經(jīng)總結(jié)過(guò)了原型的作用,原型鏈繼承是JavaScript實(shí)現(xiàn)繼承的主要方式。這種繼承的典型實(shí)現(xiàn)方式如下:
function Base(name) {// 基對(duì)象構(gòu)造函數(shù)實(shí)例成員this.name = name; }; // 基對(duì)象原型中的實(shí)例成員 Base.prototype.showName = function() {alert(this.name); }; // 子對(duì)象的構(gòu)造函數(shù) function Derived(name, age) {// 關(guān)鍵點(diǎn)1:獲取基對(duì)象構(gòu)造函數(shù)中的成員Base.call(this, name);// 定義子對(duì)象自己的成員this.age = age; }; // 關(guān)鍵點(diǎn)2:獲取基對(duì)象原型上的成員 Derived.prototype = new Base(); // 關(guān)鍵點(diǎn)3:將子對(duì)象的原型的構(gòu)造函數(shù)屬性設(shè)回正確的值 // 這樣可以防止new對(duì)象的掛接對(duì)象出錯(cuò) Derived.prototype.constructor = Derived; // 擴(kuò)展子類(lèi)自己的原型成員 Derived.prototype.showAge = function() {alert(this.age); };var d = new Derived('Frank', 10); d.showName(); d.showAge(); // 驗(yàn)證繼承關(guān)系 alert(d instanceof Base);上面的實(shí)現(xiàn)關(guān)鍵點(diǎn)已經(jīng)標(biāo)了出來(lái):
關(guān)鍵點(diǎn)1:使用call方式來(lái)調(diào)用基對(duì)象的構(gòu)造函數(shù),這樣子對(duì)象就能按照基對(duì)象構(gòu)造函數(shù)的邏輯來(lái)構(gòu)造一份基對(duì)象構(gòu)造函數(shù)中的成員。
關(guān)鍵點(diǎn)2:使用new方式來(lái)創(chuàng)建一個(gè)新的Base對(duì)象,然后把它掛到Derived對(duì)象的原型上,這樣從"Derived對(duì)象->Derived原型->Base對(duì)象->Base原型->Object對(duì)象->Object原型"就形成了鏈條了。
關(guān)鍵點(diǎn)3:new操作符要求構(gòu)造函數(shù)對(duì)象的原型的constructor屬性要指向構(gòu)造函數(shù),而在關(guān)鍵點(diǎn)2中把Derived對(duì)象的原型完全換成Base對(duì)象了,這樣有問(wèn)題了。修復(fù)這個(gè)問(wèn)題也很簡(jiǎn)單,直接把這個(gè)屬性重新設(shè)一下就行了。
上面的實(shí)現(xiàn)近乎完美,只有一個(gè)問(wèn)題:Base對(duì)象的構(gòu)造函數(shù)被調(diào)用了2次,一次在構(gòu)造函數(shù)中,一次在構(gòu)造原型鏈中,這樣的效率并不高。而根據(jù)上面的幾點(diǎn)說(shuō)明我們知道第一次調(diào)用是為了復(fù)制基對(duì)象構(gòu)造函數(shù)中的成員,必不可少;而第二次的調(diào)用純粹是為了把原型鏈接上,構(gòu)造函數(shù)中的成員并沒(méi)有使用上,于是這里就存在一個(gè)優(yōu)化的契機(jī):既然構(gòu)造函數(shù)中的成員沒(méi)有使用到,那我就用一個(gè)空對(duì)象來(lái)輔助創(chuàng)建原型鏈不就就可以了,看下面的代碼:
// 利用空對(duì)象來(lái)掛接原型鏈 function extend(Child, Parent) {var F = function(){};F.prototype = Parent.prototype;Child.prototype = new F();Child.prototype.constructor = Child; }function Base(name) {this.name = name; };Base.prototype.showName = function() {alert(this.name); };function Derived(name, age) {Base.call(this, name);this.age = age; }; // 掛接原型鏈 extend(Derived, Base); Derived.prototype.showAge = function() {alert(this.age); };var d = new Derived('Frank', 10); d.showName(); d.showAge(); alert(d instanceof Base);這個(gè)版本的實(shí)現(xiàn)據(jù)說(shuō)是YUI的實(shí)現(xiàn)繼承的方式,個(gè)人并沒(méi)參看其源代碼,有這個(gè)愛(ài)好的同學(xué)可以自行研究一下。
?
復(fù)制繼承
既然繼承的本質(zhì)是復(fù)用,那么最直接的想法應(yīng)該是復(fù)制基對(duì)象的所有成員,在JavaScript中確實(shí)可以這么做,看下面的代碼:
// 淺拷貝實(shí)現(xiàn) function extendCopy(p) {var c = {};for (var i in p) { c[i] = p[i];}return c; } // 深拷貝實(shí)現(xiàn),最為安全 function deepCopy(p, c) {var c = c || {};for (var i in p) {if (typeof p[i] === 'object') {c[i] = (p[i].constructor === Array) ? [] : {};deepCopy(p[i], c[i]);} else {c[i] = p[i];}}return c;} // 基對(duì)象 var Base = {name: 'Frank',showName: function() {alert(this.name);} } // 拷貝基對(duì)象的成員 var Derived = extendCopy(Base); // 擴(kuò)展子對(duì)象自己的成員 Derived.age = 10; Derived.showAge = function() {alert(this.age); }; // 測(cè)試基對(duì)象的成員 Derived.showName();在上面的代碼中,我們需要注意兩個(gè)問(wèn)題:
1. 我們從前面已經(jīng)了解過(guò),JavaScript有簡(jiǎn)單的“值類(lèi)型”和復(fù)雜的“引用類(lèi)型”,這樣復(fù)制成員的時(shí)候就也有所謂的“淺拷貝”和“深拷貝”的說(shuō)法。上面的代碼實(shí)現(xiàn)了兩種算法,深拷貝本質(zhì)上就是為引用類(lèi)型創(chuàng)建新的對(duì)象,這樣修改的時(shí)候就不會(huì)誤操作,修改了其他對(duì)象的成員。
2. 這種對(duì)象的擴(kuò)展方式不能使用instanceof來(lái)檢查繼承關(guān)系,例如下面的代碼是無(wú)效的:
alert(Derived instanceof Base);運(yùn)行這段代碼會(huì)返回異常:Uncaught TypeError: Expecting a function in instanceof check, but got #<Object> 。這是因?yàn)閕nstanceof只能用于檢查函數(shù)實(shí)現(xiàn)的那種繼承關(guān)系。
?
JavaScript的多態(tài)性
談完了JavaScript的繼承機(jī)制,那就不能不說(shuō)說(shuō)與之密切相關(guān)的多態(tài)性。繼承與多態(tài)從來(lái)都是面向?qū)ο笳Z(yǔ)言中不可分割的兩個(gè)概念。
由于JavaScript是腳本語(yǔ)言,動(dòng)態(tài)語(yǔ)言,所以靜態(tài)的類(lèi)型約束關(guān)系被壓縮到了極致。這一方面體現(xiàn)最為明顯的一點(diǎn)就是我們可以隨意的給對(duì)象添加和刪除成員,而另一個(gè)方面,很多語(yǔ)言都遵循“針對(duì)接口”的編程,這一點(diǎn)在動(dòng)態(tài)語(yǔ)言中的表現(xiàn)也大為不同。在JavaScript這些動(dòng)態(tài)語(yǔ)言中,我們不需要事先定義好一些接口,例如下面的例子:
var tank = {run : function () {alert('tank run');} };var person = {run : function () {alert('person run');} }; // 針對(duì)接口(run方法)的對(duì)象編程 function trigger(target) {target.run(); }trigger(tank); trigger(person);很多人對(duì)于這種使用方式不以為然,但是個(gè)人覺(jué)得這正是動(dòng)態(tài)語(yǔ)言快捷編程的特點(diǎn),很多時(shí)候還是很方便的。
轉(zhuǎn)載于:https://www.cnblogs.com/dxy1982/p/2688525.html
總結(jié)
以上是生活随笔為你收集整理的JavaScript大杂烩4 - 理解JavaScript对象的继承机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: AssetManager asset的使
- 下一篇: PHP验证码常用的函数记录