**声明:
**
最近一直在研究微前端、devops,写这篇文章仅是一个玩笑+简单的源码探究,面试时候不要拿我的文章出来问面试者,不然我怕你会被人身攻击(这个月我会出一篇硬核到头皮发麻的文章)
废话不多,直接开始,找到console的模块,找到引入的模块,进入
还是比较简单的,默认暴露globalConsole
我之前在这两个烂文章里写过(之前写的感觉就是很烂)
源码精读:通过Node.js的Cluster模块源码,深入PM2原理
原创精读:从Node.js的path模块源码,彻底搞懂webpack的路径
Node.js的源码是commonJS模块化方案,很多都是挂载到原型上提供调用,但是在现在的开发中,千万不要在原型上添加属性。
看到了Reflect.defineProperty
这些似曾相识的vue 2.x源码
里面还有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)打印完成。
觉得写得不错,记得点个赞,关注下我。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。