目錄
* 一. 概述 <https://www.cnblogs.com/dashnowords/p/10920388.html#一.-概述>
* 二. 原型鏈基礎(chǔ)知識 <https://www.cnblogs.com/dashnowords/p/10920388.html#二.-原型鏈基礎(chǔ)知識>
* 三. Worker類的原型鏈加工
<https://www.cnblogs.com/dashnowords/p/10920388.html#三.-worker類的原型鏈加工>
* 四. 實例的生成 <https://www.cnblogs.com/dashnowords/p/10920388.html#四.-實例的生成>
* 五. 最后一個問題 <https://www.cnblogs.com/dashnowords/p/10920388.html#五.-最后一個問題>
* 六. 一些心得 <https://www.cnblogs.com/dashnowords/p/10920388.html#六.-一些心得>
示例代碼托管在:http://www.github.com/dashnowords/blogs
<https://github.com/dashnowords/blogs/tree/master/Demo/nodejs/marvel-prototype>
博客園地址:《大史住在大前端》原創(chuàng)博文目錄 <https://www.cnblogs.com/dashnowords/p/10127926.html>
華為云社區(qū)地址:【你要的前端打怪升級指南】
<https://bbs.huaweicloud.com/blogs/8ae7e6420a4611e9bd5a7ca23e93a891>
好的代碼都差不多,爛的代碼卻各有各的爛法。
一. 概述
原型鏈?zhǔn)莏avascript非常重要的基礎(chǔ)知識。最近在閱讀node.js
,發(fā)現(xiàn)許多代碼乍一看會覺得很費解,但細細品味之后會覺得非常優(yōu)雅,對于代碼細節(jié)的把控和性能的考量讓人覺得贊嘆。不得不說看大師級的作品真的是一種享受。本篇中我將以
cluster模塊中子進程管理對象Worker類的實現(xiàn)為例,帶你一起看看堪稱藝術(shù)的代碼是如何像手術(shù)一樣操作原型鏈,同時理解本節(jié)的知識點對于下一篇cluster
模塊的學(xué)習(xí)壓力。
二. 原型鏈基礎(chǔ)知識
javascript中存在兩種原型概念——內(nèi)置[[prototype]]屬性指向的對象和prototype原型對象,prototype
原型對象上掛載著實例上的公共方法和屬性,[[prototype]]屬性可以通過__proto__屬性來訪問(雖然暴露了這個屬性但不推薦使用,平時更多使用
Object.getPrototypeOf( )方法來獲取,也可以通過Object.setPrototypeOf( )來修改,本文中為了書寫方便繼續(xù)用
__proto__),所一個實例的[[prototype]]屬性指向的并不一定是自己構(gòu)造方法對應(yīng)的prototype原型對象。
javascript中通過new運算符來生成對象,生成的對象的[[prototype]]
屬性會以一種串聯(lián)的方式指向多個構(gòu)造函數(shù)的原型對象,以便可以獲取可被共享使用的方法,如下所示:
當(dāng)我們需要實現(xiàn)功能繼承時,最簡單的做法就是在子類的構(gòu)造函數(shù)里生成一個父類的實例,然后令實例的__proto__
屬性指向這個實例,但這樣做會使得父類上一些本應(yīng)被添加在實例上的屬性和方法被添加到了原型鏈上,而不是真正的子類實例上,而繼承的目的主要是為了獲取父類的提供的公共的原型方法,所以
ES6的extends語法糖實現(xiàn)的繼承效果就是下面這個樣子的,后文中我們會看到Worker的原型鏈也是按照這樣的方式來修剪的:
三. Worker類的原型鏈加工
Worker的源代碼在官方倉庫的lib/internal/worker.js,代碼只有50行,用IDE折疊起來先瀏覽一下:
我們分析一下它的運作機制,首先聲明了Worker這個類,此時它對應(yīng)的原型鏈如下:
為了Worker擁有消息收發(fā)的能力,需要讓它從EventEmitter類來繼承發(fā)布訂閱能力,所以這里將EventEmitter.prototype對象添加到
Worker的原型鏈中:
Object.setPrototypeOf(Worker.prototype, EventEmitter.prototype);
這時的原型鏈就變成了下面的樣子,也就是和ES6中extends關(guān)鍵字的實現(xiàn)的繼承是一致的:
接下來的這句就有些費解,看起來好像沒起到什么作用,你可以自己思考一下,最后我們再揭曉答案:
Object.setPrototypeOf(Worker,EventEmitter);
一圖勝千言,直接看原型鏈結(jié)果:
這里的加工使得Worker構(gòu)造方法的__proto__從Worker.prototype改變到了EventEmitter
構(gòu)造方法,這使得原型鏈直接變成一個三叉形,看起來非常奇怪,而且看起來Worker和它的原型對象Worker.prototype
之間斷開了聯(lián)系,如果此時讓你生成一個worker實例,你能清楚地說出它的原型鏈?zhǔn)鞘裁礃幼訂幔?br>
我們先繼續(xù)往后看,后面的代碼在Worker.prototype上添加了一些原型方法,使得原型鏈再一次變形:
至此,原型鏈就調(diào)整結(jié)束了,下一節(jié)我們開始看Worker如何生成實例。
四. 實例的生成
worker的實例化是在lib/internal/cluster/master.js中,也就是主線程中生成子線程時調(diào)用的,調(diào)用的語句是:
const worker = new Worker({ id: id, process: workerProcess });
也就是說它是通過new操作符來生成實例的。Worker構(gòu)造方法中的核心語句如下:
function Worker(options){ if(!(this instanceof Worker)){ return new
Worker(options) } EventEmitter.call(this); }
首先對于this的判斷是用來限制Worker只能作為構(gòu)造函數(shù)使用,因為此時this會指向?qū)嵗绻鹴his并不是Worker
的實例,就說明Worker是作為方法調(diào)用的,此時會自動用new操作符來生成實例,如果你它的機制還不清楚,可以先閱讀以下Mozilla開發(fā)者文檔(
【MDN中對于new算法的描述】
<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new>
),基本算法是這樣的:
1.生成一個新的空對象; 2.將空對象的.__proto__指向構(gòu)造函數(shù)的原型對象; 3.將這個空對象綁定為this指向然后傳入構(gòu)造函數(shù)來運行;
4.如果構(gòu)造函數(shù)有返回值,則將返回值作為實例返回,如果沒有則將之前生成的空對象作為實例返回。
按照上面的描述,當(dāng)函數(shù)被執(zhí)行到Worker構(gòu)造方法的函數(shù)體中時,原型鏈?zhǔn)窍旅孢@樣的:
接下來執(zhí)行的是:
EventEmitter.call(this);
也就是將實例作為this透傳到EventEmitter構(gòu)造方法中去執(zhí)行,在官方文檔中可以找到它實際上執(zhí)行的是EventEmitter.init
方法,語句只有幾行,但非常有意思:
EventEmitter.init = function(){ if (this._events === undefined || this._events
=== Object.getPrototypeOf(this)._events) { this._events = Object.create(null);
this._eventsCount = 0; } }
如果實例上沒有_events屬性,或者它的_events屬性存在于自己的原型鏈上,那么就使用Object.create(null)
生成一個空對象,就直接在實例上添加_events屬性和_eventsCount屬性并賦值??諏ο笞置媪亢蚈bject.create(null)
生成的對象原型鏈?zhǔn)遣灰粯拥模?br>
后者生成的對象原型鏈更短,對象的本質(zhì)是一種散列結(jié)構(gòu),你新生成的對象很可能只是用來存儲一些鍵值對的映射關(guān)系而并不是為了當(dāng)做對象實例在使用,后一種結(jié)構(gòu)在查找某個屬性時需要遍歷的屬性就更少,效率也會高一些。
至此實例就生成完畢了,它最終的原型鏈?zhǔn)窍旅孢@樣的:
可以看到Worker雖然繼承了EventEmitter的消息收發(fā)能力,但是卻并沒有生成完整的EventEmitter
實例,而只是將必須擁有的實例屬性添加在了子類的實例對象上,在實現(xiàn)能力的同時也保持原型鏈結(jié)構(gòu)的最小化,避免冗余,這一波干凈利落的原型鏈加工真的太秀了,不得不說
node.js的細節(jié)處理真的堪稱藝術(shù)。
五. 最后一個問題
前面我們還遺留了一個問題,還記得嗎?
Object.setPrototypeOf(Worker,EventEmitter)
你可以很清楚地看到實例的原型鏈和上面這條語句實現(xiàn)的功能沒什么關(guān)系。事實上它的作用是為了讓子類繼承父類的靜態(tài)方法,一張圖就能解決的問題,我就不再多bibi了:
這里的目的就是為了盡可能完整地實現(xiàn)面向?qū)ο蟮奶匦?,使得你可以直接通過Worker構(gòu)造函數(shù)來訪問到EventEmitter
上的靜態(tài)屬性和方法,你可以在本文提供的demo中看到。
六. 一些心得
閱讀經(jīng)典源碼是一個非常緩慢且吃力的事情,尤其是沒人帶沒人交流時,但是如果開始了,就請一定保持耐心。比如上面的代碼僅僅是cluster
模塊中很小的一部分,只有短短50行,如果基礎(chǔ)薄弱可能要花很久才能消化其中的東西,但是它能夠教給你的原型鏈知識和對開發(fā)細節(jié)的把控能力,是你讀5000行垃圾代碼也無法學(xué)習(xí)到的。
熱門工具 換一換