python入門python的基本語法
584
2025-04-04
示例代碼托管在:http://www.github.com/dashnowords/blogs
博客園地址:《大史住在大前端》原創博文目錄
華為云社區地址:【你要的前端打怪升級指南】
【nodejs原理&源碼賞析(3)】欣賞手術級的原型鏈加工藝術一. 概述二. 原型鏈基礎知識三. Worker類的原型鏈加工四. 實例的生成五. 最后一個問題六. 一些心得
好的代碼都差不多,爛的代碼卻各有各的爛法。
一. 概述
原型鏈是javaScript非常重要的基礎知識。最近在閱讀node.js,發現許多代碼乍一看會覺得很費解,但細細品味之后會覺得非常優雅,對于代碼細節的把控和性能的考量讓人覺得贊嘆。不得不說看大師級的作品真的是一種享受。本篇中我將以cluster模塊中子進程管理對象Worker類的實現為例,帶你一起看看堪稱藝術的代碼是如何像手術一樣操作原型鏈,同時理解本節的知識點對于下一篇cluster模塊的學習壓力。
二. 原型鏈基礎知識
javaScript中存在兩種原型概念——內置[[prototype]]屬性指向的對象和prototype原型對象,prototype原型對象上掛載著實例上的公共方法和屬性,[[prototype]]屬性可以通過__proto__屬性來訪問(雖然暴露了這個屬性但不推薦使用,平時更多使用Object.getPrototypeOf( )方法來獲取,也可以通過Object.setPrototypeOf( )來修改,本文中為了書寫方便繼續用__proto__),所一個實例的[[prototype]]屬性指向的并不一定是自己構造方法對應的prototype原型對象。
javascript中通過new運算符來生成對象,生成的對象的[[prototype]]屬性會以一種串聯的方式指向多個構造函數的原型對象,以便可以獲取可被共享使用的方法,如下所示:
當我們需要實現功能繼承時,最簡單的做法就是在子類的構造函數里生成一個父類的實例,然后令實例的__proto__屬性指向這個實例,但這樣做會使得父類上一些本應被添加在實例上的屬性和方法被添加到了原型鏈上,而不是真正的子類實例上,而繼承的目的主要是為了獲取父類的提供的公共的原型方法,所以ES6的extends語法糖實現的繼承效果就是下面這個樣子的,后文中我們會看到Worker的原型鏈也是按照這樣的方式來修剪的:
三. Worker類的原型鏈加工
Worker的源代碼在官方倉庫的lib/internal/worker.js,代碼只有50行,用IDE折疊起來先瀏覽一下:
我們分析一下它的運作機制,首先聲明了Worker這個類,此時它對應的原型鏈如下:
為了Worker擁有消息收發的能力,需要讓它從EventEmitter類來繼承發布訂閱能力,所以這里將EventEmitter.prototype對象添加到Worker的原型鏈中:
Object.setPrototypeOf(Worker.prototype,?EventEmitter.prototype);
這時的原型鏈就變成了下面的樣子,也就是和ES6中extends關鍵字的實現的繼承是一致的:
接下來的這句就有些費解,看起來好像沒起到什么作用,你可以自己思考一下,最后我們再揭曉答案:
Object.setPrototypeOf(Worker,EventEmitter);
一圖勝千言,直接看原型鏈結果:
這里的加工使得Worker構造方法的__proto__從Worker.prototype改變到了EventEmitter構造方法,這使得原型鏈直接變成一個三叉形,看起來非常奇怪,而且看起來Worker和它的原型對象Worker.prototype之間斷開了聯系,如果此時讓你生成一個worker實例,你能清楚地說出它的原型鏈是什么樣子嗎?
我們先繼續往后看,后面的代碼在Worker.prototype上添加了一些原型方法,使得原型鏈再一次變形:
至此,原型鏈就調整結束了,下一節我們開始看Worker如何生成實例。
四. 實例的生成
worker的實例化是在lib/internal/cluster/master.js中,也就是主線程中生成子線程時調用的,調用的語句是:
const worker = new Worker({
id: id,
process: workerProcess
});
也就是說它是通過new操作符來生成實例的。Worker構造方法中的核心語句如下:
function Worker(options){
if(!(this instanceof Worker)){
return new Worker(options)
}
EventEmitter.call(this);
}
首先對于this的判斷是用來限制Worker只能作為構造函數使用,因為此時this會指向實例,如果this并不是Worker的實例,就說明Worker是作為方法調用的,此時會自動用new操作符來生成實例,如果你它的機制還不清楚,可以先閱讀以下Mozilla開發者文檔(【MDN中對于new算法的描述】),基本算法是這樣的:
1.生成一個新的空對象;
2.將空對象的.__proto__指向構造函數的原型對象;
3.將這個空對象綁定為this指向然后傳入構造函數來運行;
4.如果構造函數有返回值,則將返回值作為實例返回,如果沒有則將之前生成的空對象作為實例返回。
按照上面的描述,當函數被執行到Worker構造方法的函數體中時,原型鏈是下面這樣的:
接下來執行的是:
EventEmitter.call(this);
也就是將實例作為this透傳到EventEmitter構造方法中去執行,在官方文檔中可以找到它實際上執行的是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屬性并賦值。空對象字面量和Object.create(null)生成的對象原型鏈是不一樣的:
后者生成的對象原型鏈更短,對象的本質是一種散列結構,你新生成的對象很可能只是用來存儲一些鍵值對的映射關系而并不是為了當做對象實例在使用,后一種結構在查找某個屬性時需要遍歷的屬性就更少,效率也會高一些。
至此實例就生成完畢了,它最終的原型鏈是下面這樣的:
可以看到Worker雖然繼承了EventEmitter的消息收發能力,但是卻并沒有生成完整的EventEmitter實例,而只是將必須擁有的實例屬性添加在了子類的實例對象上,在實現能力的同時也保持原型鏈結構的最小化,避免冗余,這一波干凈利落的原型鏈加工真的太秀了,不得不說node.js的細節處理真的堪稱藝術。
五. 最后一個問題
前面我們還遺留了一個問題,還記得嗎?
Object.setPrototypeOf(Worker,EventEmitter)
你可以很清楚地看到實例的原型鏈和上面這條語句實現的功能沒什么關系。事實上它的作用是為了讓子類繼承父類的靜態方法,一張圖就能解決的問題,我就不再多bibi了:
這里的目的就是為了盡可能完整地實現面向對象的特性,使得你可以直接通過Worker構造函數來訪問到EventEmitter上的靜態屬性和方法,你可以在本文提供的demo中看到。
六. 一些心得
閱讀經典源碼是一個非常緩慢且吃力的事情,尤其是沒人帶沒人交流時,但是如果開始了,就請一定保持耐心。比如上面的代碼僅僅是cluster模塊中很小的一部分,只有短短50行,如果基礎薄弱可能要花很久才能消化其中的東西,但是它能夠教給你的原型鏈知識和對開發細節的把控能力,是你讀5000行垃圾代碼也無法學習到的。
附件: marvel-prototype.rar 1.47KB 下載次數:0次
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。