**聲明:
**前端
最近一直在研究微前端、devops,寫這篇文章僅是一個玩笑+簡單的源碼探究,面試時候不要拿個人文章出來問面試者,否則我怕你會被人身攻擊(這個月我會出一篇硬核到頭皮發麻的文章)vue
廢話很少,直接開始,找到console的模塊,找到引入的模塊,進入webpack
仍是比較簡單的,默認暴露globalConsolees6
我以前在這兩個爛文章裏寫過(以前寫的感受就是很爛)web
源碼精讀:經過Node.js的Cluster模塊源碼,深刻PM2原理 面試
原創精讀:從Node.js的path模塊源碼,完全搞懂webpack的路徑 模塊化
Node.js的源碼是commonJS模塊化方案,不少都是掛載到原型上提供調用,可是在如今的開發中,千萬不要在原型上添加屬性。函數
看到了Reflect.defineProperty oop
這些似曾相識的vue 2.x源碼this
裏面還有ES6的Reflect.ownKeys得到全部屬性集合
Reflect.getOwnPropertyDescriptor獲得屬性描述符
還不瞭解的能夠看
https://es6.ruanyifeng.com/#docs/reflect
這段入口的代碼:
const globalConsole = Object.create({}); for (const prop of Reflect.ownKeys(Console.prototype)) { if (prop === 'constructor') { continue; } const desc = Reflect.getOwnPropertyDescriptor(Console.prototype, prop); if (typeof desc.value === 'function') { // fix the receiver desc.value = desc.value.bind(globalConsole); } Reflect.defineProperty(globalConsole, prop, desc); } globalConsole[kBindStreamsLazy](process); globalConsole[kBindProperties](true, 'auto'); globalConsole.Console = Console; module.exports = globalConsole;
核心邏輯:
1.先生成一個純淨的對象
2.遍歷原型上的屬性 若是是構造函數就跳過
3.獲取它的訪問描述符,從新生成掛載到desc(訪問描述符上)
4.相似vue 2.x的源碼實現,使用下面的API,指定屬性讀取劫持,例如我使用console.log時候,就會觸發 Reflect.defineProperty(globalConsole, prop, desc)
5.真正的原理在後面,constructor的Console上
看看引入的Console是什麼
熟悉的味道,掛載到的是原型上。
先看核心代碼:
for (const method of Reflect.ownKeys(consoleMethods)) Console.prototype[method] = consoleMethods[method]; Console.prototype.debug = Console.prototype.log; Console.prototype.info = Console.prototype.log; Console.prototype.dirxml = Console.prototype.log; Console.prototype.error = Console.prototype.warn; Console.prototype.groupCollapsed = Console.prototype.group; module.exports = { Console, kBindStreamsLazy, kBindProperties };
發現consoleMethods就是咱們想要的
遍歷了一次,將consoleMethods的方法都拷貝到了Console的原型上,這樣咱們就能夠調用console.log了
那麼log方法怎麼實現的呢?
log(...args){ this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); },
最終是靠this.kWriteToConsole,也就是Console實現(kWriteToConsole是一個Symbol臨時屬性)
關鍵這裏kUseStdout也是一個Symbol臨時屬性,kFormatForStdout有一丟丟繞,咱們看看kFormatForStdout
Console.prototype[kFormatForStdout] = function(args) { const opts = this[kGetInspectOptions](this._stdout); return formatWithOptions(opts, ...args); };
這裏是對顏色作一個處理,不作過分處理,都在本模塊內,聲明的map類型內存儲
Console.prototype[kGetInspectOptions] = function(stream) { let color = this[kColorMode]; if (color === 'auto') { color = stream.isTTY && ( typeof stream.getColorDepth === 'function' ? stream.getColorDepth() > 2 : true); } const options = optionsMap.get(this); if (options) { if (options.colors === undefined) { options.colors = color; } return options; } return color ? kColorInspectOptions : kNoColorInspectOptions; };
處理完打印顏色配置,進入最終函數:
Console.prototype[kWriteToConsole] = function(streamSymbol, string) { const ignoreErrors = this._ignoreErrors; const groupIndent = this[kGroupIndent]; const useStdout = streamSymbol === kUseStdout; const stream = useStdout ? this._stdout : this._stderr; const errorHandler = useStdout ? this._stdoutErrorHandler : this._stderrErrorHandler;
這裏咱們須要重點觀察下stream這個值,在這個模塊出現過不少次,咱們看看其餘地方(跟本文的源碼無關)
const stream = streamSymbol === kUseStdout ? instance._stdout : instance._stderr;
官方註釋:
// This conditional evaluates to true if and only if there was an error
意思是出現錯誤時候,打印error。
if (ignoreErrors === false) return stream.write(string); try { // Add and later remove a noop error handler to catch synchronous errors. if (stream.listenerCount('error') === 0) stream.once('error', noop); stream.write(string, errorHandler);
最終使用stream.write(string)打印完成。
以爲寫得不錯,記得點個贊,關注下我。