無情面試官:Node.js源碼裏的console.log怎麼實現的?

**聲明:
**前端

最近一直在研究微前端、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)打印完成。

以爲寫得不錯,記得點個贊,關注下我。

相關文章
相關標籤/搜索