**声明:
**前端
最近一直在研究微前端、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)打印完成。
以为写得不错,记得点个赞,关注下我。