使用原型鏈來構(gòu)建項目架構(gòu)

使用原型鏈來構(gòu)建項目架構(gòu)

原型和原型鏈

一點(diǎn)廢話

? 現(xiàn)在網(wǎng)上關(guān)于原型鏈這一部分的博客大多是從類繼承模型開始說的。因為 javascript 的繼承模型區(qū)別于很多傳統(tǒng)語言的類繼承称近,它使用的是 prototype 原型模型模擬出繼承的效果吟秩。但是我是做IC前端設(shè)計出身的红碑,想我這種本來就對與類繼承不熟悉的人說這個區(qū)別意義也不大。我之前僅僅接觸過面向過程的編程寓免,從JAVA胸梆,pathon之類的高級語言過渡到j(luò)avascript上面自然而然就會有提到兩者的區(qū)別。對于沒有接觸過的面向?qū)ο缶幊痰娜藖碚f的話最好先能夠?qū)ο笫玻约懊嫦驅(qū)ο蟮木幊逃忠粋€大致的了解蝙叛。因為只有先對“對象”在一個項目里面的意義有所了解俺祠,才會明白我們?yōu)槭裁葱枰玫接迷玩溁蛘咂渌氖裁捶绞絹順?gòu)建項目架構(gòu)公给。到但是如果我要開始從對象開始說的話,這篇文章就會過長蜘渣,所以我假設(shè)正在讀這篇文章的你有著還不錯的邏輯思維能力淌铐,但是對于高級語言這點(diǎn)破事又不太清楚。那么蔫缸,我們開始吧腿准。

原型鏈

? 如果你之前有過編程經(jīng)驗。那么拾碌,我相信你一定知道我們會把一些反復(fù)用到的功能封裝成一個函數(shù)⊥麓校現(xiàn)在,除了函數(shù)以外校翔,在面向?qū)ο蟮恼Z言里面弟跑,還引入了對象這個概念。對于 javascript 來說除了 nullundefined 以外都是對象防症。每一個對象都會有對應(yīng)的屬性或者方法孟辑。那么,當(dāng)我們訪問對象的屬性的時候蔫敲,如果這個對象不具有這種屬性饲嗽,那么編譯器就會順著原型鏈向上尋找,一個對象除了會擁有自己的屬性以外奈嘿,還會繼承來自原型鏈上層的父級對象的屬性貌虾,所以編譯器會一直順著原型鏈向上尋找,直到找到那個屬性或者到達(dá)原型鏈末尾裙犹。但是在實(shí)際操作中這樣做會實(shí)際上非常消耗資源尽狠,因為這需要遍歷整個原型鏈,所以很多時候還是使用hasOwnProperty 方法伯诬,這是官方欽定的唯一一種不需要遍歷整個原型鏈就能對屬性進(jìn)行處理的方法晚唇。

上面說的這種依次向上尋找的尋找屬性的方法就是 javascript 中的繼承方式

//// 假定有一個對象 o, 其自身的屬性(own properties)有 a 和 b:
// {a: 1, b: 2}
// o 的原型 o.[[Prototype]]有屬性 b 和 c:
// {b: 3, c: 4}
// 最后, o.[[Prototype]].[[Prototype]] 是 null.
// 這就是原型鏈的末尾,即 null盗似,
// 根據(jù)定義哩陕,null 沒有[[Prototype]].
// 綜上,整個原型鏈如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> null
console.log(o.a); // 1
// a是o的自身屬性嗎?是的悍及,該屬性的值為1

console.log(o.b); // 2
// b是o的自身屬性嗎闽瓢?是的,該屬性的值為2
// o.[[Prototype]]上還有一個'b'屬性,但是它不會被訪問到.這種情況稱為"屬性遮蔽 (property shadowing)".

console.log(o.c); // 4
// c是o的自身屬性嗎心赶?不是扣讼,那看看o.[[Prototype]]上有沒有.
// c是o.[[Prototype]]的自身屬性嗎?是的,該屬性的值為4

console.log(o.d); // undefined
// d是o的自身屬性嗎缨叫?不是,那看看o.[[Prototype]]上有沒有.
// d是o.[[Prototype]]的自身屬性嗎椭符?不是,那看看o.[[Prototype]].[[Prototype]]上有沒有.
// o.[[Prototype]].[[Prototype]]為null耻姥,停止搜索销钝,
// 沒有d屬性,返回undefined

上面提到的是關(guān)于原型鏈?zhǔn)且环N理想化的模型琐簇。便于理解蒸健,在通常的情況下,每個函數(shù)都有一個原型屬性 prototype 指向自己的原型婉商,而由這個函數(shù)創(chuàng)建的對象也有一個 _proto_ 屬性指向這個原型似忧。上面說的有點(diǎn)繞,我從另一個角度來說明原型鏈盒原型吧丈秩。先從下面這張圖開始說

o_1aom2dgfs16b73c3tpa1ie06s89.jpg

我們可以看到這里面有 _proto_prototype 這兩個東西盯捌。如果你有使用過chrome 的develop tools 或者vscode進(jìn)行單步調(diào)試的經(jīng)驗的話你會經(jīng)常看到 *_proto_* 這個字段癣籽。那么挽唉,這個到底是什么意思呢

_proto_protptype 兩個有什么關(guān)系與區(qū)別呢?

先看 _proto_ 開始說起

每一個JS對象一定對應(yīng)一個原型對象筷狼,并且從原型對象那里繼承屬性和方法瓶籽。(重點(diǎn))

這話不是我說的,看下面

Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.

看完了以后再看下面這個例子

var one = {x: 1};
var two = new Object();
one._proto_ === Object.protopyte //true
two._proto_ === Object.prototype //true
one.toSting === one._protpto_.toString //true

在上面兩個例子里面埂材,one 和 two 兩個對象的原型對象全等于對自己當(dāng)前對象的 _proto_ 屬性塑顺。更進(jìn)一步來說,one 的自己的繼承來自己原型的方法的時候也可以用 _proto_ 來找到俏险。但是這也引入了一個新問題严拒。為什么one 和 two 的原型對象就是Object.protopyte,還有 Object.protopyte 究竟是個啥竖独?

上面這個問題先放一下裤唠,等我講完了prototype再放到一起看

首先,prototype 和 _proto_ 的第一個區(qū)別就在于:每一個對象都會有一個 _proto_ 屬性來標(biāo)示自己所繼承的原型莹痢。但是函數(shù)才會有 prototype 屬性种蘸。當(dāng)我們創(chuàng)建函數(shù)的時候墓赴,JS 會為這個函數(shù)追加一個 prototype 屬性。當(dāng)我們嘗試把這個函數(shù)當(dāng)成一個構(gòu)造函數(shù)來調(diào)用的時候航瞭,那么 JS 就會創(chuàng)建這個構(gòu)造函數(shù)的實(shí)例诫硕,這個事例會繼承構(gòu)造函數(shù) prototype 的所有屬性和方法。同時實(shí)例會通過 _proto_ 指向構(gòu)造函數(shù)的 prototype 刊侯。

于是 JS 就是這樣通過 _proto_ 和 prototype 來實(shí)現(xiàn)原型鏈章办。

構(gòu)造函數(shù)就是通過 prototype 來保存要共享給實(shí)例的屬性和方法。

對象的 _proto_ 總是指向自己的構(gòu)造函數(shù)的 prototype 滨彻。 大概可以這樣描述一下

obj._proto_._proto_ === Constructor.prototype 

之類的藕届。

最后再補(bǔ)充一下,原型鏈的頂端就是 Object.prototype 疮绷。因為在之前的圖片里面 Object.prototype 的上層 _proto_ 指向的是 null

君與this與apply與call那點(diǎn)破事

在討論完原型鏈之后翰舌,還有一個繞不開的問題就是 this 的指向問題,還有 apply 和 call 到底是咋回事的問題冬骚。

this

最開始接觸到 this 是在廖雪峰的教程里面,當(dāng)時只記得 this 有設(shè)計缺陷啥的懂算,理解也不是特別深刻只冻。我現(xiàn)在常識對之前的問題祖宗一個總結(jié),嘗試用一種簡單的方式并且全面的把這個部分講清楚计技。

如果你嘗試尋找網(wǎng)上關(guān)于this的資料喜德,那么很大的概率最終會找到這么一篇文章 ,說真的看了這篇文章之后我完全不知道怎么往下寫。畢竟這篇文章說的太好了垮媒。這篇文章分別在全局對象舍悯,函數(shù),原型睡雇,方法中 this 具體的指向問題萌衬。堪稱教科書級別的文章它抱。但是我覺得還是有必要自己的方法去描述一下這個概念秕豫。

就像從零開始寫一個 cpu 那樣,我嘗試使用一種增量模型來描述 this 這個概念观蓄。

  1. “ this 指向當(dāng)前對象”

    我們姑且先這樣記住混移,然后按照每個特出情況再對這句話進(jìn)行一點(diǎn)修改,讓最后的表述最接近真實(shí)的 this .(會不會有點(diǎn)小題大做了侮穿?)那么在瀏覽器宿主的全局環(huán)境下歌径,this 所指向的對象就是對象 window

    console.log(this === Window) // true
    
  2. 在函數(shù)中使用 this 的時候,不使用 new 關(guān)鍵字聲明函數(shù)的時候 this 仍然指向 window亲茅,當(dāng)使用 new 的時候指向這個函數(shù)回铛。

    //普通的聲明
    foo = "bar";
    function xx(){
      this.foo = "fuck";
    }
    
    console.log(this.foo); //bar
    new xx();
    console.log(this.foo); //bar
    console.log(new xx().foo) //fuck
    

    是不是看的有點(diǎn)暈金矛?為什么單獨(dú)new 沒變化,log出去就改變了this的值呢勺届?我們得先知道new的時候編譯器干了啥

    英文好的讀 這個 驶俊。下面的你就不用看了,英文不好的接著看免姿。下面是不負(fù)責(zé)任的翻譯:

    new干了5件事

    1. 創(chuàng)造一個對象
    2. 把構(gòu)造函數(shù)的 prototype 拷貝到 實(shí)例對象的 _proto_ 中饼酿。
    3. 讓 this 這個變量指向新創(chuàng)建的對象
    4. 使用新創(chuàng)見的對象執(zhí)行構(gòu)造函數(shù)
    5. 最后返回新創(chuàng)建的對象

    下面是兩個例子

    ObjMaker = function() {this.a = 'first';};
    // 我叫 ObjMaker 是一個普通的構(gòu)造函數(shù)
    ObjMaker.prototype.b = 'second';
    // 和所有的函數(shù)一樣,我有很多可以改變的原型屬性(prototype property)胚膊,現(xiàn)在我被增加了一個叫 b 的屬性故俐。
    // 與此同時,我還是一個對象.那么作為一個對象紊婉,我也會有很多不可訪問的內(nèi)部屬性([[prototype]] property)
    obj1 = new ObjMaker();
    // 現(xiàn)在我作為一個構(gòu)造函數(shù)構(gòu)造了一個叫obj1的對象
    // 發(fā)生了三件事
    // 1. 一個全新的叫obj1的空對象背創(chuàng)建了药版。類似于這樣 obj1 = {}
    // 2. obj1 的[[prototype]] 屬性被設(shè)置為ObjMaker.prototype(如果ObjMaker.prototype在此之后又作為構(gòu)造 函數(shù)生成了一個新對象,obj1 的 [[prototype]] 不會改變但是你可以通過修改ObjMaker.porototype來添加原型)
    // 3. 執(zhí)行這個函數(shù)
    

    附上new的執(zhí)行代碼

    function New(func) {
        var res = {};
        if (func.prototype !== null) {
            res.__proto__ = func.prototype;
        }
        var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
        if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
            return ret;
        }
        return res;
    }
    

    上面雖然 new 了一個新的函數(shù)喻犁,但是這個構(gòu)造函數(shù)并沒有指向找到合適的對象返回出去槽片,而在調(diào)用console.log的時候相當(dāng)于生成了一個新的對象,然后把這個沒有名字的新生成的對象打印出去了肢础。

    說到這还栓,基本上new的東西就講完了,如果想有更近一步的了解传轰,你可以去看一下廖雪峰的教程 剩盒。(我還是覺得了廖雪峰的教程不是特別適合完全的新手看。)new 就先說到這慨蛙,我們接著看this相關(guān)的內(nèi)容

  3. 在原型中指向當(dāng)前對象辽聊,但是在原型鏈中原型鏈底層函數(shù)中對this的操作會覆蓋上層的值

    function Thing1() {
    }
    Thing1.prototype.foo = "bar";
    
    function Thing2() {
        this.foo = "foo";
    }
    Thing2.prototype = new Thing1();
    
    function Thing3() {
    }
    Thing3.prototype = new Thing2();
     var thing = new Thing3();
    
       console.log(thing.foo); //logs "foo"
       //原型鏈 thing -> Thing3 -> Thing2 -> Thing1
       //每一次new 操作都會讓 this 跳轉(zhuǎn)一次,當(dāng)編譯器在查找方法或者屬性的時候會依次向上尋找期贫,
       //當(dāng)編譯器在thing2這層找到foo屬性的時候就停止查找跟匆,這樣就屏蔽了Thing1這一層的foo屬性
    
    

實(shí)際上上面這個例子也是 JS 在模擬經(jīng)典的對象繼承

apply與call

終于講到 apply 了。其實(shí)這兩個在有了上面的基礎(chǔ)以后唯灵,一句話就能說清楚贾铝。apply 和 call 就是用于改變函數(shù)內(nèi)部 this 的指向。當(dāng)某個對象埠帕,比如A對象有 a 方法垢揩,B對象有 b 方法,那么如果想讓A對象也具有b方法敛瓷,那么使用apply或者call就可以完成這樣一個操作叁巨。

call和apply都是Function.prototype 下面的一個方法,所有的函數(shù)都具備這兩種方法呐籽,這兩種方法都具有相同的效果锋勺,唯一的區(qū)別在于調(diào)用方式不同蚀瘸。我們用一個簡單的例子來說明問題。

function ghost(){};//我們定義一個鬼兵
ghost.prototype = {
  EMP : function(){
    //emp code here
  },
  HasSnipe : 0庶橱,
  Say   : function(){
    console.log("Waiting on you");
  }
}                    //這是一個升級了EMP但是沒有升級狙擊贮勃,有一句臺詞的的鬼兵。

var nova = new ghost;//諾娃是一個光榮的帝國鬼兵苏章,具有鬼兵的技能寂嘉。
var Scott = {};//Scott是一個新兵蛋子,他剛接受了帝國鬼兵的訓(xùn)練枫绅,可以使用技能泉孩,但是系統(tǒng)并沒有為他分配武器。
           //然而在偵查到一個小隊滿能量的哨兵并淋,于是諾娃的武器給Scott了寓搬,
           //命令Scott去使用EMP去清空那些哨兵的能量與護(hù)盾。
           //在系統(tǒng)層面县耽,我們這么做
Scott.EMP.call(nova);

然后諾娃掏出了她的大吊大刀把那些燒餅干掉了句喷,就是這樣。

實(shí)際上在這個例子已經(jīng)很清楚了酬诀。更進(jìn)一步的描述在于這里

apply

call

下面抄一個正式一點(diǎn)的例子脏嚷,為那些不玩SC的同學(xué)們準(zhǔn)備的

function Man() {}
Man.prototype = {
    valet: false,
    wakeUp: function(event) {
        console.log(this.valet + "? Some breakfase, please.");
    }
};

//get "undefined? Some breakfast, please
var button = document.getElementById('morning');
button.addEventListener(
    "click",
    wooster.wakeUp,
    false
);

//使用apply來改變 wakeUp 的上下文環(huán)境,即 wakeUp 中的this
var button = document.getElementById('morning2');
button.addEventListener(
    "click",
    function() {
        Man.prototype.wakeUp.apply(wooster, arguments);
    },
    false
);

擴(kuò)展閱讀

http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html

https://segmentfault.com/a/1190000002634958

https://developer.mozilla.org/en/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

http://bonsaiden.github.io/JavaScript-Garden/zh/#object.prototype

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞒御,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子神郊,更是在濱河造成了極大的恐慌肴裙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涌乳,死亡現(xiàn)場離奇詭異蜻懦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)夕晓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門宛乃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒸辆,你說我怎么就攤上這事征炼。” “怎么了躬贡?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵谆奥,是天一觀的道長。 經(jīng)常有香客問我拂玻,道長酸些,這世上最難降的妖魔是什么宰译? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮魄懂,結(jié)果婚禮上沿侈,老公的妹妹穿的比我還像新娘。我一直安慰自己市栗,他們只是感情好缀拭,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肃廓,像睡著了一般智厌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盲赊,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天铣鹏,我揣著相機(jī)與錄音,去河邊找鬼哀蘑。 笑死诚卸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绘迁。 我是一名探鬼主播合溺,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缀台!你這毒婦竟也來了棠赛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤膛腐,失蹤者是張志新(化名)和其女友劉穎睛约,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哲身,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辩涝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勘天。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怔揩。...
    茶點(diǎn)故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脯丝,靈堂內(nèi)的尸體忽然破棺而出商膊,到底是詐尸還是另有隱情,我是刑警寧澤巾钉,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布翘狱,位于F島的核電站,受9級特大地震影響砰苍,放射性物質(zhì)發(fā)生泄漏潦匈。R本人自食惡果不足惜阱高,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茬缩。 院中可真熱鬧赤惊,春花似錦、人聲如沸凰锡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掂为。三九已至裕膀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勇哗,已是汗流浹背昼扛。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欲诺,地道東北人抄谐。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像扰法,于是被迫代替她去往敵國和親蛹含。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評論 2 361

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