濃縮解讀《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》①

f2deb48f8c5494ee6870cfaf2af5e0fe99257e32.jpg

面向?qū)ο蟮腏avaScript

1.1 動(dòng)態(tài)類型語言和鴨子類型

  • 按照數(shù)據(jù)類型,編程語言可以分為兩大類:靜態(tài)類型語言動(dòng)態(tài)類型語言 。
  • 靜態(tài)類型語言在編譯時(shí)就已確定變量的類型,而動(dòng)態(tài)類型語言要在程序運(yùn)行時(shí),等變量被賦值后,才確定數(shù)據(jù)類型。
  • 靜態(tài)類型語言的優(yōu)點(diǎn):
    1. 可以幫助開發(fā)者在編譯時(shí)檢查類型錯(cuò)誤;
    2. 在運(yùn)行前明確了數(shù)據(jù)類型,編譯器可以針對(duì)程序進(jìn)行優(yōu)化,提升性能;
  • 靜態(tài)類型語言的缺點(diǎn):強(qiáng)迫開發(fā)者依照契約編寫程序,繁雜的類型聲明會(huì)增加更多的代碼,分散開發(fā)者的精力。
  • 動(dòng)態(tài)類型語言的優(yōu)點(diǎn): 編寫的代碼量更少,利于閱讀,讓開發(fā)者更專注于業(yè)務(wù)邏輯。
  • 動(dòng)態(tài)類型語言的缺點(diǎn):
    1. 不區(qū)分?jǐn)?shù)據(jù)類型的情況下,可能會(huì)讓程序難以理解;
    2. 由于無法保證變量的數(shù)據(jù)類型,運(yùn)行期間可能會(huì)發(fā)生于類型相關(guān)的錯(cuò)誤;
  • JavaScript是典型的動(dòng)態(tài)類型語言,在對(duì)變量賦值時(shí)不需要考慮它的類型。動(dòng)態(tài)類型語言對(duì)變量類型的寬容給實(shí)際編碼帶來了很大的靈活性,這一切構(gòu)建在鴨子類型(duck typing)的概念上。
  • 鴨子類型有這樣一個(gè)故事:國(guó)王要組建一個(gè)100只鴨子組成的合唱團(tuán),找到99只鴨子了,還差一只。最后發(fā)現(xiàn)有一只非常特別的雞,叫聲跟鴨子一模一樣,最后把這只雞加入了合唱團(tuán)。通俗的說法是指,“如果它走起來像鴨子,叫起來也是鴨子,那么它就是鴨子”。鴨子類型指導(dǎo)思想是說“應(yīng)該關(guān)注對(duì)象的行為,而不是對(duì)象的本身。也就是說要關(guān)注HAS-A,而不是IS-A”。(注意單詞has 和 is)
  • 鴨子類型的概念在動(dòng)態(tài)類型語言的面相對(duì)象設(shè)計(jì)中至關(guān)重要,通過鴨子類型可以實(shí)現(xiàn)一個(gè)原則“面向接口編程,而不是面向?qū)崿F(xiàn)編程”。例如,一個(gè)對(duì)象如果擁有push和po方法,它可以被當(dāng)做棧來使用;一個(gè)對(duì)象如果有l(wèi)ength屬性,且可以通過下標(biāo)對(duì)屬性進(jìn)行贈(zèng)刪改查,那這個(gè)對(duì)象就可以當(dāng)做數(shù)組來使用。(如果A擁有某個(gè)對(duì)象的接口方法,那就可以認(rèn)為A是對(duì)象的實(shí)例)
  • 由于JavaScript中“面向接口編程”的過程與主流的靜態(tài)類型語言不一樣,導(dǎo)致JavaScript在實(shí)現(xiàn)設(shè)計(jì)模式的過程也與主流的靜態(tài)類型語言大相徑庭。

1.2 多態(tài)

1.2.1 什么是多態(tài)?

  • polymorphism [?p?l?'m?:f?z?m](多態(tài))一詞源于希臘文,拆解開來是poly(復(fù)數(shù))和morph(形態(tài))兩個(gè)單詞的。它實(shí)際含義是指“同樣一個(gè)操作,作用于不同的對(duì)象,可以產(chǎn)生不同的解釋,并返回不同的執(zhí)行結(jié)果”。
  • 用多態(tài)來舉例:主人有一只貓和一只狗,當(dāng)主任向它們發(fā)出“叫”的命令時(shí),不同的動(dòng)物會(huì)以自己的方式來發(fā)出叫聲,狗會(huì)汪汪叫,貓會(huì)喵喵叫。

//定義一個(gè)發(fā)聲的方法,傳入一個(gè)animal參數(shù)
var makeSound = function(animal){
//判斷animal的實(shí)例是什么動(dòng)物,就發(fā)出什么叫聲
if(animal instanceof Gog){
console.info("汪汪汪");
}else if(animal instanceof Cat){
console.info("喵喵喵");
}
}
//定義兩個(gè)動(dòng)物
var Dog = function(){};
var Cat = function(){};
//調(diào)用方法
makeSound(new Dog());
makeSound(new Cat());

但示例1存在這樣的問題:如果要增加一個(gè)動(dòng)物(比如牛),則必須要改動(dòng)makeSound函數(shù)了。要知道修改代碼總是危險(xiǎn)的,修改的地方越多,程序出錯(cuò)的可能性就越大。并且當(dāng)動(dòng)物種類越來越多,makeSound函數(shù)將變得非常巨大。
- 而多態(tài)背后的核心思想是`“將不變的事物和可能變化的事物分離開來”`。例子中,動(dòng)物都會(huì)叫是不變的,但不同類型的動(dòng)物具體怎么叫是可變的,我們可以將不變的部分隔離出來,把可變的部分封裝起來,讓程序符合開放-封閉原則。

//統(tǒng)一的makeSound函數(shù)調(diào)用入口
var markSound = function(animal){
//將具體怎么叫,封裝成動(dòng)物的方法
animal.sound();
};
//鴨子
var Duck = function(){ };
Duck.prototype.sound = function(){
console.info("嘎嘎嘎");
};
//小雞
var Chicken = function(){ };
Chicken.prototype.sound = function(){
console.info("咯咯咯");
};
makeSound(new Duck()); //嘎嘎嘎
makeSound(new Chicken()); //咯咯咯


#### 1.2.2 多態(tài)引發(fā)的類型檢查問題

- 談到多態(tài),類型檢查是繞不開的話題,但JavaScript是一門不比進(jìn)行類型檢查的動(dòng)態(tài)類型語言,不像靜態(tài)類型語言。
- 以Java為例,代碼編譯時(shí)會(huì)進(jìn)行嚴(yán)格的類型檢查,不能給變量賦予不同類型的值。

String str; //定義一個(gè)String類型的變量
str = "abc"; //賦值成功
str = 123; //報(bào)錯(cuò)!

通過Java實(shí)現(xiàn)鴨子類型:

//聲明一個(gè)鴨子類
public class Duck{
public void makeSound(){
System.out.println("嘎嘎嘎");
}
}
//聲明一個(gè)小雞類
public class Chicken{
public void makeSound(){
System.out.println("咯咯咯");
}
}
//呼叫動(dòng)物類
public class AnimalSound{
public void makeSound(Duck duck){
duck.makeSound();
}
}
//測(cè)試類
public class Test{
public static void main(String args[]){
AnimalSound animalSound = new AnimalSound();
animalsound.makeSound(new Duck()); //輸出:嘎嘎嘎
}
}

雖然順利讓鴨子發(fā)出叫聲,但如果想讓小雞叫喚,發(fā)現(xiàn)幾乎是不可能實(shí)現(xiàn),因?yàn)锳nimal.makeSound()方法規(guī)定了只接受Duck類型的參數(shù)。
針對(duì)這種情況,靜態(tài)類型的編程語言通常被設(shè)計(jì)成可以`向上轉(zhuǎn)型`:當(dāng)給一個(gè)類變量賦值時(shí),既可以使用類本身,也可以使用這個(gè)類的超類。好比我們描述天上的一直麻雀時(shí),既可以說“有一只麻雀在天上飛”,也可以說“有一只鳥在天上飛”。
- 通過繼承實(shí)現(xiàn)多態(tài)的效果是最常用的手段。繼承通常包含`實(shí)現(xiàn)繼承`和`接口繼承`,這里通過實(shí)現(xiàn)繼承重新調(diào)整鴨子類型的代碼。

//定義動(dòng)物抽象類
public abstract class Animal{
abstract void makeSound(); //makeSound抽象方法
}
//小雞實(shí)現(xiàn)類
public class Chicken extends Animal{
public void makeSound(){
System.out.println("咯咯咯");
}
}
//小鴨實(shí)現(xiàn)類
public class Duck extends Animal{
public void makeSound(){
System.out.println("嘎嘎嘎");
}
}
//呼叫動(dòng)物類
public class AnimalSound{
public void makeSound(Animal animal){
animal.makeSound();
}
}
//測(cè)試類
public class Test{
public static void main(String args[]){
AnimalSound animalSound = new AnimalSound();
animalsound.makeSound(new Duck()); //輸出:嘎嘎嘎
animalsound.makeSound(new Chicken()); //輸出:咯咯咯
}
}


#### 1.2.3 JavaScript的多態(tài)
- 多態(tài)之所以要把“不變的事物”和“可能改變的事物”分離,是為了消除類型之間的耦合,Java通過向上轉(zhuǎn)型來實(shí)現(xiàn)。而由于JavaScript的變量類型在運(yùn)行期是可變的,意味著JavaScript對(duì)象的多態(tài)性是與生俱來的 。判斷動(dòng)物是否能發(fā)出叫聲,不需要判斷對(duì)象是某種類型的動(dòng)物,只取決于它有沒有makeSound方法,不存在任何程度的“類型耦合”。

#### 1.2.4 多態(tài)在面向?qū)ο蟪绦蛟O(shè)計(jì)中的作用
- 多態(tài)最根本的作用是消除條件分之語句,將過程化的條件分之語句轉(zhuǎn)化為對(duì)象的多態(tài)性。
- Martin Fowler在《重構(gòu):改善既有代碼的設(shè)計(jì)》書中以拍電影作為多態(tài)的比喻。
* 電影在拍攝時(shí),當(dāng)導(dǎo)演喊出“action”后,主演門開始講臺(tái)詞,燈光師負(fù)責(zé)打燈,群眾演員假裝中槍倒地,道具師往鏡頭撒雪花。在等到導(dǎo)演的指令后,每個(gè)對(duì)象都知道自己應(yīng)該做什么,這就是多態(tài)性。
* 如果不利用對(duì)象的多態(tài)性,而是用面向過程的方式上來編寫代碼,那么就相當(dāng)于:每次電影開始拍攝后,導(dǎo)演要逐個(gè)走到每個(gè)人的面前,確認(rèn)它們的職業(yè)分工(類型),然后再告訴他們要做什么。映射到程序當(dāng)中,那么程序中將充斥著大量條件分之語句。
- 每個(gè)對(duì)象應(yīng)該做什么,封裝在成對(duì)象內(nèi)部的一個(gè)方法,每個(gè)對(duì)象負(fù)責(zé)自己的行為。所以這些對(duì)象可以根據(jù)同一個(gè)指令,有條不紊地分別進(jìn)行各自的工作,這正是面向?qū)ο蟮膬?yōu)點(diǎn)。

#### 1.2.5 多態(tài)與設(shè)計(jì)模式
- GoF的《設(shè)計(jì)模式》一書從面向?qū)ο笤O(shè)計(jì)的角度出發(fā),通過對(duì)封裝、繼承、多態(tài)、組合等多種技術(shù)的反復(fù)使用,提煉出可重復(fù)使用的面向?qū)ο笤O(shè)計(jì)技巧。多態(tài)是當(dāng)中的重中之重,絕大多數(shù)設(shè)計(jì)模式的實(shí)現(xiàn)都離不開多態(tài)性的思想。
- 比如命令模式,請(qǐng)求被封裝在一些命令對(duì)象中,這使得命令的調(diào)用和命令的接受者可以完全解耦開來,當(dāng)調(diào)用execute方法時(shí),不同的命令做不同的事情,從而產(chǎn)生不同的執(zhí)行結(jié)果。
- 在組合模式中,對(duì)組合對(duì)象和葉節(jié)點(diǎn)對(duì)象發(fā)出同一個(gè)指令時(shí),它們會(huì)各自做自己應(yīng)該做的事情,組合對(duì)象把消息繼續(xù)傳遞給下面的葉節(jié)點(diǎn)對(duì)象,葉節(jié)點(diǎn)再對(duì)指令做出響應(yīng)。
- 在策略模式中,Context并沒有執(zhí)行算法的能力,而是把職責(zé)委托給具體的策略對(duì)象。每個(gè)策略對(duì)象負(fù)責(zé)的算法被封裝在各自對(duì)象的內(nèi)部。當(dāng)對(duì)這些策略對(duì)象發(fā)出計(jì)算的指令時(shí),它們會(huì)個(gè)各自執(zhí)行并響應(yīng)不同的計(jì)算結(jié)果。

---

### 1.3 封裝
#### 1.3.1 封裝數(shù)據(jù)
- 很多編程語言是通過語法解析來實(shí)現(xiàn)封裝數(shù)據(jù)的,比如Java提供private、public、protected等關(guān)鍵字來限定訪問權(quán)限。
- 可JavaScript缺乏這些關(guān)鍵字的支持,只能依賴變量的作用域來實(shí)現(xiàn)public和private的封裝特性。

var myObject = (function(){
var _name = 'sven'; //私有變量
return {
//公開方法
getName : function(){
return _name;
}
};
})();

- (ECMAScripte6標(biāo)準(zhǔn),提供了let關(guān)鍵字來創(chuàng)建塊級(jí)作用域)

#### 1.3.2 封裝實(shí)現(xiàn)
- 很多人喜歡把封裝理解成封裝數(shù)據(jù),這是一種狹義的定義。其實(shí)封裝不僅是隱藏?cái)?shù)據(jù),還包括隱藏實(shí)現(xiàn)細(xì)節(jié)、設(shè)計(jì)細(xì)節(jié)以及隱藏對(duì)象的類型等。
- 封裝實(shí)現(xiàn)細(xì)節(jié)指的是,使得對(duì)象內(nèi)部的變化對(duì)于其他對(duì)象而言是不可見的,對(duì)象只對(duì)自己的行為負(fù)責(zé)。對(duì)象之間的耦合變松散,對(duì)象之間只通過暴露的API接口來通訊。這樣一來,即便當(dāng)我們需要修改對(duì)象時(shí),可以任意修改它的內(nèi)部實(shí)現(xiàn),而由于對(duì)外接口沒有變化,則不會(huì)影響程序的其他功能。
- 比如迭代器each()函數(shù),不用關(guān)心它的內(nèi)部是怎么實(shí)現(xiàn)的,只需要知道它的作用是遍歷集合對(duì)象。及時(shí)each函數(shù)修改了內(nèi)部源代碼,主要調(diào)用方式?jīng)]有變化,就不會(huì)對(duì)調(diào)用each()函數(shù)的代碼造影響。

#### 1.3.3 封裝類型
- 封裝類型是類似Java等靜態(tài)類型語言中一種重要的封裝方式。一般通過抽象類和接口來進(jìn)行。把對(duì)象的真正類型隱藏在抽象類或者接口背后,這樣對(duì)于調(diào)用者來說,就看看呀只關(guān)心對(duì)象的行為,而不是對(duì)象的類型。
- 由于靜態(tài)語言需要想方設(shè)法的隱藏對(duì)象的類型,也促使了比如工廠方法模式、組合模式等設(shè)計(jì)模式的誕生。
- JavaScript本身是一門類型模糊的語言。對(duì)于JavaScript的設(shè)計(jì)模式實(shí)現(xiàn)來說,不區(qū)分類型是一種失色,也可以說是一種解脫。

#### 1.3.4 封裝變化
- 從設(shè)計(jì)模式的角度出發(fā),封裝的更高層面體現(xiàn)為封裝變化。
- 《設(shè)計(jì)模式》提到“找到變化,并封裝之”,《設(shè)計(jì)模式》一書中總共歸納總結(jié)了23中設(shè)計(jì)模式,這23種設(shè)計(jì)模式又可以從意圖上區(qū)分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式。
- 拿創(chuàng)建型模式來說,具體創(chuàng)建什么對(duì)象是變化的,創(chuàng)建型模式的目的就是封裝創(chuàng)建對(duì)象的變化。而結(jié)構(gòu)型模式封裝的是對(duì)象之間的組合關(guān)系。行為型模式封裝的是對(duì)象的行為變化。通過封裝變化,可以最大程度的保證程序的穩(wěn)定性和可拓展性。

---

### 1.4 原型模式和JavaScript
- Brendan Eich設(shè)計(jì)JavaScript時(shí),之所以選擇原型的面向?qū)ο笙到y(tǒng),是因?yàn)閺囊婚_始就沒有打算在JavaScript中加入類的概念。
- 以類作為中心的面向?qū)ο缶幊陶Z言當(dāng)中,比如Java,類和對(duì)象的關(guān)系可以想象成鑄模和鑄件的關(guān)系,對(duì)象是從類中創(chuàng)建而來的。而在原型編程的思想當(dāng)中,類不是必須的,對(duì)象是通過克隆另一個(gè)對(duì)象得到的。

#### 1.4.1 使用克隆的原型模式
- 原型模式是創(chuàng)建對(duì)象的一種模式。
- 相比起Java,創(chuàng)建一個(gè)對(duì)象要先指定它的類型,然后通過類來創(chuàng)建這個(gè)對(duì)象。原型模式不在關(guān)心對(duì)象的具體類型,而是找到一個(gè)對(duì)象,然后通過克隆來創(chuàng)建一個(gè)一模一樣的對(duì)象,就好比游戲中的分身。
- 說到克隆,原型模式的實(shí)現(xiàn)關(guān)鍵在于編程語言是否提供clone方法,比如ECMAScript5提供的`Object.create()`方法。

#### 1.4.2 克隆是創(chuàng)建對(duì)象的手段
- 原型模式的真正目的并不是為了復(fù)制一個(gè)一模一樣的對(duì)象,而是提供一種便捷的方式去創(chuàng)建某個(gè)類型的對(duì)象,而克隆只是創(chuàng)建對(duì)象的手段。
- 依賴倒置原則提醒開發(fā)者,編寫像Java等靜態(tài)型語言的程序時(shí),創(chuàng)建對(duì)象要避免依賴具體的類型,比如`new XXX`創(chuàng)建對(duì)象的方式會(huì)使得類型之間的耦合度很高,代碼很僵硬。需要通過工廠方法模式和抽象工廠模式解決此類問題,但這無可避免的,會(huì)增加很多額外的代碼。
- 原型模式則提供了另外一種方式,通過克隆對(duì)象,不需要再關(guān)心對(duì)象的具體類型名稱,所以也就不存在類型耦合的問題。
- JavaScript本身是一門基于原型的面型對(duì)象語言,它的面向?qū)ο笙到y(tǒng)通過原型模式來搭建,所以與其稱為原型模式,不如稱之為原型編程范例更為合適。

#### 1.4.3 原型模式的Io語言
- 事實(shí)上,使用原型模式來構(gòu)建面向?qū)ο笙到y(tǒng)的編程語言,并非僅有JavaScript一家。還有比如Self語言、Smalltalk語言,以及另一個(gè)輕巧的Io語言。
- Io中同樣沒有類的概念每個(gè)對(duì)象都是基于另外一個(gè)對(duì)象的克隆。既然每個(gè)對(duì)象都是由其他對(duì)象克隆而來,那么Io語言本身應(yīng)該至少要提供一個(gè)根對(duì)象,其他對(duì)象都發(fā)源于這個(gè)根對(duì)象才對(duì),就好像美劇吸血鬼的始祖一樣。對(duì)的,Io語言根對(duì)象是Object。
- 繼續(xù)拿動(dòng)物世界的例子講解Io語言

//通過克隆Object根對(duì)象得到Animal對(duì)象,所以O(shè)bejct稱為Animal的原型
Animal := Object clone;
//給Animal對(duì)象添加makeSound方法
Animal makeSound := method("animal makeSound" print);
//接下來以Animal作為原型,繼續(xù)創(chuàng)建Dog對(duì)象
Dog := Animal clone;
//然后給Dog對(duì)象添加eat方法
Dog eat := method("dog eat" print);
//最后測(cè)試Animal對(duì)象和Dog對(duì)象的功能
Animal makeSound; //輸出"animal makeSound"
Dog eat; //輸出"dog eat"


#### 1.4.4 原型編程的特點(diǎn)
- 從Io語言的使用當(dāng)中可看出,跟使用“類”的語言不同的是,原型編程語言最初只有一個(gè)根對(duì)象(Object),其他對(duì)象都是克隆自另一個(gè)對(duì)象。
- 在上一個(gè)例子當(dāng)中,Object是Animal的原型,Animal的Dog的原型,它們串聯(lián)起來形成了一條原型鏈。
- 原型鏈?zhǔn)呛苡杏锰幍?,?dāng)嘗試調(diào)用Dog對(duì)象的某個(gè)方法,而它本身又沒有時(shí),那么Dog對(duì)象會(huì)把調(diào)用的請(qǐng)求委托給它的原型Animal對(duì)象。如果Animal對(duì)象也沒有的話,請(qǐng)求會(huì)繼續(xù)順著原型鏈,被委托給Object對(duì)象。這樣一來,便能得到繼承的效果,看起了就像是Animal是Dog的父類,Object是Animal的父類。這個(gè)機(jī)制并不復(fù)雜卻非常強(qiáng)大,JavaScript和Io語言一樣,原型繼承的本質(zhì)就是基于原型鏈的委托機(jī)制。
- 最后我們觀察發(fā)現(xiàn),原型編程泛型包括以下的特點(diǎn):
  * 所有的數(shù)據(jù)都是對(duì)象;
  * 不通過實(shí)例化創(chuàng)建對(duì)象,而是找到一個(gè)對(duì)象作為原型并克隆它;
  * 對(duì)象會(huì)記住個(gè)各自的原型;
  * 如果對(duì)象無法響應(yīng)某個(gè)請(qǐng)求,會(huì)把這個(gè)請(qǐng)求委托給自己的原型;

#### 1.4.5 原型模式的JavaScript語言
- 接下來講解JavaScript如何基于原型編程的規(guī)則來構(gòu)建面向?qū)ο笙到y(tǒng)。
- __所有的數(shù)據(jù)都是對(duì)象__:
    * JavaScript在設(shè)計(jì)的時(shí)候模仿了Java,數(shù)據(jù)類型分為基本類型和對(duì)象類型。

    * 基本數(shù)據(jù)類型有boolean、number、string、null、undefined。
    * 按照J(rèn)avaScript設(shè)計(jì)者的本意,除了undefined之外,其他都是對(duì)象。而為了實(shí)現(xiàn)這一目標(biāo),基本數(shù)據(jù)類型也可以通過“包裝類”的方式變成對(duì)象類型來處理。

- __不通過實(shí)例化創(chuàng)建對(duì)象,而是找到一個(gè)對(duì)象作為原型并克隆它__:
    * 相比起Io語言,JavaScript中不需要關(guān)心克隆的細(xì)節(jié),JavaScript引擎內(nèi)部會(huì)處理。我們只需要顯式的調(diào)用`var obj1 = new Object();`或者`var obj2 = {};`,JavaScript引擎就會(huì)從Object.prototype上克隆一個(gè)對(duì)象出來。
    * 演示用new運(yùn)算符從構(gòu)造器中得到一個(gè)對(duì)象
    ```
function Person(name){
        this.name = name;
};
Person.prototype.getName = function(){
        return this.name;
};
var person1 = new Person('William');
console.log(person1.name);   //輸出”William“
console.log(person1.getName());   //輸出”William“
console.log(Object.getPrototypeOf(person1) === Person.prototype);   //輸出"true“
    ```  

    * 這里的Person不是類,而是構(gòu)造函數(shù)。JavaScript的函數(shù)既可以作為普通函數(shù)被調(diào)用,也可以作為構(gòu)造函數(shù)被調(diào)用。當(dāng)使用new關(guān)鍵字調(diào)用函數(shù)時(shí),此函數(shù)就是一個(gè)構(gòu)造器。
    * 當(dāng)你使用new操作符調(diào)用構(gòu)造函數(shù)時(shí),會(huì)經(jīng)歷以下步驟:
      1. 創(chuàng)建一個(gè)空對(duì)象,作為將要返回的實(shí)例對(duì)象;
      2. 將空對(duì)象的原型指向構(gòu)造函數(shù)的prototype屬性,也就是Keith構(gòu)造函數(shù)的prototype屬性;
      3. 將空對(duì)象賦值給構(gòu)造函數(shù)內(nèi)部的this關(guān)鍵字,也就是this關(guān)鍵字會(huì)指向?qū)嵗龑?duì)象;
      4. 開始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼;
- __對(duì)象會(huì)記住個(gè)各自的原型__:
    * 要實(shí)現(xiàn)Io語言或者JavaScript語言中的原型鏈查找機(jī)制,每個(gè)對(duì)象至少應(yīng)該先記住自己的原型對(duì)象。
    * 但就JavaScript真正的實(shí)現(xiàn)來說,其實(shí)說對(duì)象有原型并我準(zhǔn)確,應(yīng)該說對(duì)象的構(gòu)造器有原型。對(duì)于“對(duì)象把請(qǐng)求委托給自己的原型”這句話,更好的說法應(yīng)該是“對(duì)象把請(qǐng)求委托給它的構(gòu)造器的原型”。
    ```
person1.constructor指向Person,然后Person.prototype指向原型對(duì)象;
或者
person1.[[Prototype]]指向Person.Person.prototype原型對(duì)象([[Prototype]]屬性和書中所寫的__proto__屬性一致)
    ```
- __如果對(duì)象無法響應(yīng)某個(gè)請(qǐng)求,會(huì)把這個(gè)請(qǐng)求委托給自己的原型__:
    * Io語言中每個(gè)對(duì)象都可以作為原型被克隆,Animal對(duì)象克隆自O(shè)bject對(duì)象,Dog對(duì)象又克隆自Animal對(duì)象,形成了一條天然的原型鏈。但這樣就只是單一的繼承連,這樣的面向?qū)ο笙到y(tǒng)顯得非常受限。
    * 實(shí)際上JavaScript的對(duì)象最初都是由Object.prototype對(duì)象克隆而來,但不受限于Obejct.prototype,而是可以動(dòng)態(tài)的指向其他對(duì)象。
    * 在原型鏈查找機(jī)制中,原型鏈并不是無限長(zhǎng)的。當(dāng)嘗試訪問對(duì)象的某個(gè)屬性,請(qǐng)求會(huì)被委托給各自的原型對(duì)象,如果最終傳遞到Object.prototype對(duì)象也沒有查找到。這次請(qǐng)求會(huì)就此打住,返回undefined。

#### 1.4.6 原型繼承的未來
- 設(shè)計(jì)模式很多時(shí)候其實(shí)是在彌補(bǔ)語言的不足之處,就像Peter Norvig曾說,設(shè)計(jì)模式是對(duì)語言不足的補(bǔ)充,如果要使用設(shè)計(jì)模式,不如去找一門更好的語言。
- JavaScript中用`Object.create()`來完成原型繼承,看起來更能體現(xiàn)原型模式的精髓。但效率卻不高,比通過構(gòu)造函數(shù)來創(chuàng)建對(duì)象要慢。而ECMAScript6帶來了新的Class語法。

class Animal{
constructor(name){
this.name = name;
}
getName(){
return this.name;
}
}
class Dog extends Animal{
constructor(name){
super(name);
}
speak(){
return "woof";
}
}
var dog = new Dog("Scamp");
console.log(dog.getName() + ' says' + dog.speak());

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容