繼承或者委托

一 概覽

??這篇文章是我通過閱讀《JavaScript 高級程序設(shè)計(jì)》和《你不知道的JavaScript》中關(guān)于 繼承 模塊的一點(diǎn)心得。

二 面向?qū)ο蠡仡?/h3>

面向類編程

??你是否還記得大學(xué)里面剛學(xué)C++的時候關(guān)于面向?qū)ο蟮慕榻B呢辜纲,讓我們一塊來回顧一下吧默责。
??類的 定義:在面向?qū)ο缶幊讨械媛保愂且环N 代碼組織結(jié)構(gòu)形式,一種從真實(shí)世界到軟件設(shè)計(jì)的建模方法。
??類的 組織形式:面向?qū)ο蠡蛘呙嫦蝾惥幊虖?qiáng)調(diào) 數(shù)據(jù)操作數(shù)據(jù)的行為 應(yīng)該 封裝 在一起,在正式計(jì)算機(jī)科學(xué)中我們稱為 數(shù)據(jù)結(jié)構(gòu)玻靡。

類與23種高級設(shè)計(jì)模式

??類是面向?qū)ο蟮?底層設(shè)計(jì)模式,它是面向?qū)ο?3種高級設(shè)計(jì)模式的 底層機(jī)制中贝。
??你或許還聽說過 過程化編程,一種不借助高級抽象囤捻,僅僅由 過程(函數(shù))調(diào)用 來組織代碼的編程方式。程序語言中邻寿,Java只支持面向類編程蝎土,C/C++/Php既支持過程化編程,也支持面向類編程绣否。

類的機(jī)制

??在類的設(shè)計(jì)模式中誊涯,它為我們提供了 實(shí)例化繼承蒜撮、多態(tài) 3種機(jī)制暴构。
??構(gòu)造器:類的實(shí)例由類的一種特殊方法構(gòu)建,這個方法的名稱通常與類名相同段磨,稱為 “構(gòu)造器(constructor)”取逾。這個方法的明確的工作,就是初始化實(shí)例所需的所有信息(狀態(tài))苹支。
??實(shí)例化:借助構(gòu)造函數(shù)菌赖,由通用類到具體對象的過程。
??繼承:子類通過 拷貝(請一定要記住這個詞)父類的屬性和方法沐序,從而使自己也能擁有這些屬性與方法的過程琉用。
??多態(tài):由繼承產(chǎn)生的堕绩,子類重寫從父類中繼承的屬性和方法,從而子類更加具體邑时。

類的繼承

(1)相對多態(tài):任何方法都可以引用位于繼承層級上更高一層的其他方法(同名或不同名)奴紧。我們說“相對”,因?yàn)槲覀儾唤^對定義我們想訪問繼承的哪一層(也就是類)晶丘,而實(shí)質(zhì)上在說“向上一層”來相對地引用黍氮。
(2)超類:在許多語言中,使用 super 關(guān)鍵字來引用 父類或祖先類浅浮。
(3)如果子類覆蓋父類的某個方法沫浆,原版的方法和覆蓋后的方法都是可以存在的,允許訪問滚秩。
(4) 不要讓多態(tài)搞糊涂专执,子類并不是鏈接到父類上,子類只是父類的一個副本郁油,類繼承的實(shí)質(zhì)是拷貝行為本股。
(5)多重繼承:子類的父類不止一個,JavaScript不支持多重繼承桐腌。

混入

原理: 子構(gòu)造函數(shù)混入父構(gòu)造函數(shù)的屬性和方法拄显。
JavaScript的復(fù)合類型以 引用 的方式傳遞,不支持拷貝行為。混入(Mixin)手動拷貝 的方式模擬繼承的拷貝行為案站。

明確混入:

(1)定義:顯示的把一個對象的屬性混入另一個對象躬审。
(2)實(shí)現(xiàn)如下:

// 另一種mixin,對覆蓋不太“安全”
// 大幅簡化的`mixin(..)`示例:
function mixin( sourceObj, targetObj ) {
    for (var key in sourceObj) {
        // 僅拷貝非既存內(nèi)容
        if (!(key in targetObj)) {
            targetObj[key] = sourceObj[key];
        }
    }

    return targetObj;
}

var Vehicle = {
    engines: 1,

    ignition: function() {
        console.log( "Turning on my engine." );
    },

    drive: function() {
        this.ignition();
        console.log( "Steering and moving forward!" );
    }
};

var Car = mixin( Vehicle, {
    wheels: 4,

    drive: function() {
        Vehicle.drive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    }
} );

(3)顯示假想多態(tài):Vehicle.drive.call(this)蟆盐。因?yàn)镋S6之前盒件,JavaScript無法實(shí)現(xiàn)相對多態(tài)(inherit:drive()),所以我們明確地用名稱指出Vehicle對象舱禽,然后在它上面調(diào)用drive()函數(shù)。
(4)問題
??A.技術(shù)上講恩沽,函數(shù)沒有被復(fù)制誊稚,只是復(fù)制了函數(shù)的引用;
??B.在每一個需要建立 假想多態(tài) 引用的函數(shù)中都需要建立手動鏈接(Vehicle.drive.call(this))罗心,維護(hù)成本高里伯。可以嘗試通過它實(shí)現(xiàn) 多重繼承渤闷。
(5)結(jié)論:明確混入復(fù)雜疾瓮、難懂、維護(hù)成本高飒箭,不推薦使用狼电。

寄生繼承:

(1)明確的mixin模式的一個變種蜒灰,在某種意義上是明確的而在某種意義上是隱含的。
(2)實(shí)現(xiàn)如下:在子構(gòu)造函數(shù)中new一個如果找函數(shù)的實(shí)例對象肩碟,在這個對象上擴(kuò)展屬性强窖、方法,最后將這個對象返回削祈。

// “傳統(tǒng)的JS類” `Vehicle`
function Vehicle() {
    this.engines = 1;
}
Vehicle.prototype.ignition = function() {
    console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
    this.ignition();
    console.log( "Steering and moving forward!" );
};

// “寄生類” `Car`
function Car() {
    // 首先, `car`是一個`Vehicle`
    var car = new Vehicle();

    // 現(xiàn)在, 我們修改`car`使它特化
    car.wheels = 4;

    // 保存一個`Vehicle::drive()`的引用
    var vehDrive = car.drive;

    // 覆蓋 `Vehicle::drive()`
    car.drive = function() {
        vehDrive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    };
    return car;
}

var myCar = new Car();

myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!

(3)問題:子函數(shù)的初始化創(chuàng)建對象丟失翅溺,改變了this綁定,不過不用new去直接創(chuàng)建髓抑。

隱式混入

(1)定義:父咙崎、子構(gòu)造函數(shù)在原有構(gòu)造函數(shù)與屬性、方法之間吨拍,添加一層函數(shù)褪猛,子構(gòu)造函數(shù)中間函數(shù)的this綁定到父構(gòu)造函數(shù)中間函數(shù)
(2)實(shí)現(xiàn)原理:利用了this的二次綁定。
(3) 實(shí)現(xiàn)如下:

var Something = {
    cool: function() {
        this.greeting = "Hello World";
        this.count = this.count ? this.count + 1 : 1;
    }
};

Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1

var Another = {
    cool: function() {
        // 隱式地將`Something`混入`Another`
        Something.cool.call( this );
    }
};

Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (不會和`Something`共享狀態(tài))

(4) 問題:單純的利用this的二次綁定密末,不能實(shí)現(xiàn)相對應(yīng)用握爷。
(5) 結(jié)論:謹(jǐn)慎使用。

三 原型

prototype

prototype 定義:JavaScript中每個對象都擁有一個prototype屬性严里,它只是一個 其他對象的引用新啼。幾乎所有的對象在被創(chuàng)建時,它的這個屬性都被賦予了一個 非null 值刹碾。

“類”函數(shù)

代碼如下:

function Foo() {
    // ...
}

var a = new Foo();

Object.getPrototypeOf( a ) === Foo.prototype; // true

結(jié)論: 當(dāng)通過調(diào)用new Foo()創(chuàng)建實(shí)例對象時燥撞,實(shí)例對象會被鏈接到Foo.prototype指向的對象。

拷貝與鏈接

代碼如下:

function Foo() {

}

Foo.prototype.fruit = ['apple'];

// foo1的[prototype]鏈接到了 Foo.prototype
var foo1 = new Foo();
foo1.fruit.push('banana');

// foo2的[prototype]也被鏈接到了 Foo.prototype
var foo2 = new Foo();
foo2.fruit // [apple, banana]

??在面向類的語言中迷帜,可以創(chuàng)造一個類的多個拷貝物舒。在JavaScript中,我們不能創(chuàng)造一個類的多個實(shí)例戏锹,可以創(chuàng)建多個對象冠胯,它們的[prototype]鏈接指向一個共同對象。但默認(rèn)地锦针,沒有拷貝發(fā)生荠察,如此這些對象彼此間最終不會完全分離和切斷關(guān)系,而是 鏈接在一起奈搜。

??“繼承”意味著 拷貝 操作悉盆,而JavaScript不拷貝對象屬性(原生上,默認(rèn)地)馋吗。相反焕盟,JS在兩個對象間建立鏈接,一個對象實(shí)質(zhì)上可以將對屬性/函數(shù)的訪問 委托 到另一個對象上宏粤。對于描述JavaScript對象鏈接機(jī)制來說脚翘,“委托”是一個準(zhǔn)確得多的術(shù)語灼卢。

new調(diào)用和普通調(diào)用本質(zhì)相同

??JavaScript中,new在某種意義上劫持了普通函數(shù)堰怨,并將它以另一種函數(shù)調(diào)用:構(gòu)建一個對象芥玉,外加調(diào)用這個函數(shù)所做的任何事。

實(shí)例對象沒有constructor屬性
function Foo() { }

var foo1 = new Foo();

foo1.constructor === Foo  // true

// 修改Foo.prototype指向的對象
Foo.prototype = {
    //
}

var foo2 = new Foo();

foo2.constructor === Foo  // false

??a.constructor === Foo為true意味著a上實(shí)際擁有一個.constructor屬性备图,指向Foo灿巧?不對
??實(shí)際上揽涮,.constructor引用也 委托 到了Foo.prototype抠藕,它 恰好 有一個指向Foo的默認(rèn)屬性。

3 “原型繼承”

原型繼承分析

代碼如下:


function Foo(name) {
    this.name = name;
}

Foo.prototype.myName = function() {
    return this.name;
};

function Bar(name,label) {
    // 構(gòu)造函數(shù)內(nèi)部相對多態(tài)
    Foo.call( this, name );
    this.label = label;
}

// 這里蒋困,我們創(chuàng)建一個新的`Bar.prototype`鏈接鏈到`Foo.prototype`
Bar.prototype = Object.create( Foo.prototype );

// 注意盾似!現(xiàn)在`Bar.prototype.constructor`不存在了,
// 如果你有依賴這個屬性的習(xí)慣的話雪标,可以被手動“修復(fù)”零院。
Bar.prototype.myLabel = function() {
    return this.label;
};

var a = new Bar( "a", "obj a" );

a.myName(); // "a"
a.myLabel(); // "obj a"

核心代碼分析
代碼1:


function Bar(para1, para2) {
   Foo.call(this, para1);
   //...
}

代碼1分析:構(gòu)造函數(shù)內(nèi)部初始化,利用this綁定村刨,根據(jù)父構(gòu)造函數(shù)初始化子構(gòu)造函數(shù)內(nèi)部告抄。

代碼2:

Bar.prototype = Object.create(Foo.prototype)

代碼2分析:原型初始化,將子構(gòu)造函數(shù)的[prototype]鏈接到父構(gòu)造函數(shù)的[prototype]鏈接的對象嵌牺。

誤區(qū)

Bar.prototype = Foo.prototype

這種方法是錯誤的打洼,子構(gòu)造函數(shù)會污染到父構(gòu)造函數(shù)

ES6 新方法

Object.setPrototypeOf(Bar.prototype, Foo.prototype)
“自身”

??面向類語言中,根據(jù)實(shí)例對象查找創(chuàng)建它的類模板逆粹,稱為自誓即(或反射)。JavaScript中僻弹,如何根據(jù)實(shí)例對象阿浓,查找它的委托鏈接呢?

1 instanceOf:

代碼如下:

function Foo() {
    //...
}

var a = new Foo();

a instanceOf Foo  // true

代碼分析:
a: instanceOf 機(jī)制蹋绽,在實(shí)例對象(a)的原型鏈中芭毙,是否有Foo.prototype;
b: 需要用于可檢測的構(gòu)造函數(shù)(Foo);
c: 無法判斷實(shí)例對象間(比如a蟋字,b)是否通過[prototype]鏈相互關(guān)聯(lián)。

2 isPrototypeOf [[prototype]]反射

代碼如下:

function Foo() {
    // ...
}

var a = new Foo();

Foo.prototype.isPrototypeOf(a);  // true

// 對象b是否在a的[[prototype]]鏈出現(xiàn)過
b.isPrototypeOf(a);

代碼分析
a:在實(shí)例對象(a)的原型鏈中扭勉,是否有Foo.prototype鹊奖;
b:需要用于可檢測的構(gòu)造函數(shù)(Foo);
c:可以判斷對象間是否通過[prototype]鏈相互關(guān)聯(lián)涂炎。

3 getPrototypeOf 獲取原型鏈

代碼如下:

function Foo() {
    // ...
}

var a = new Foo();

Object.getPrototypeOf(a)  // 查看constructor屬性

4 proto

代碼如下:

function Foo() {
    // ...
}

var a = new Foo();

a.__proto__ === Foo.prototype  // true

代碼分析:
a:proto屬性在ES6被標(biāo)準(zhǔn)化忠聚;
b:proto屬性跟 constructor屬性類似设哗,它不存在實(shí)例對象中。constructor屬性存在于 原型鏈中,proto存在于Object.prototype中两蟀。
c:proto看起來像一個屬性网梢,但實(shí)際上將它看做是一個getter/setter更合適。

Object.defineProperty( Object.prototype, "__proto__", {
    get: function() {
        return Object.getPrototypeOf( this );
    },
    set: function(o) {
        // setPrototypeOf(..) as of ES6
        Object.setPrototypeOf( this, o );
        return o;
    }
} );

對象關(guān)聯(lián)

創(chuàng)建關(guān)聯(lián)

代碼如下:

var foo = {
    printFoo: function() {
        console.log('foo');
    }
}

var a = Object.create(foo);

a.printFoo();  // 'foo'

代碼分析:
a赂毯、Object.create()會創(chuàng)建一個對象(a)战虏,并把它鏈接到指定對象(foo);
b党涕、相比new 調(diào)用烦感,Object.create()不會產(chǎn)生 prototype引用constructor引用

關(guān)聯(lián)是否備用

代碼如下:

var anotherObject = {
    cool: function() {
        console.log('cool');
    }
}

var bar = Object.create(anotherObject);

bar.cool();  // 'cool'

代碼分析:
a膛堤、單純的在bar無法處理屬性或方法時手趣,建立備用鏈接(anotherObject),代碼會變得很難理解和維護(hù)肥荔,這種模式應(yīng)該慎重使用绿渣;
b、ES6提供“代理”功能燕耿,它實(shí)現(xiàn)的就是“方法無法找到”時的行為中符。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缸棵,隨后出現(xiàn)的幾起案子舟茶,更是在濱河造成了極大的恐慌,老刑警劉巖堵第,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吧凉,死亡現(xiàn)場離奇詭異,居然都是意外死亡踏志,警方通過查閱死者的電腦和手機(jī)阀捅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來针余,“玉大人饲鄙,你說我怎么就攤上這事≡惭悖” “怎么了忍级?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伪朽。 經(jīng)常有香客問我轴咱,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任朴肺,我火速辦了婚禮窖剑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戈稿。我一直安慰自己西土,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布鞍盗。 她就那樣靜靜地躺著需了,像睡著了一般。 火紅的嫁衣襯著肌膚如雪橡疼。 梳的紋絲不亂的頭發(fā)上援所,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音欣除,去河邊找鬼住拭。 笑死,一個胖子當(dāng)著我的面吹牛历帚,可吹牛的內(nèi)容都是我干的滔岳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼挽牢,長吁一口氣:“原來是場噩夢啊……” “哼谱煤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起禽拔,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刘离,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后睹栖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硫惕,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年野来,在試婚紗的時候發(fā)現(xiàn)自己被綠了恼除。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡曼氛,死狀恐怖豁辉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舀患,我是刑警寧澤徽级,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站聊浅,受9級特大地震影響餐抢,放射性物質(zhì)發(fā)生泄漏堵幽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一弹澎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧努咐,春花似錦苦蒿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竿屹,卻和暖如春报强,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拱燃。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工秉溉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碗誉。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓召嘶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哮缺。 傳聞我的和親對象是個殘疾皇子弄跌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容