我也写了一个console相关的npm包
核心主要采用链式调用方式打印log,降低学习成本。有这个想法是,前段时间看到一位大佬写的文章下的一条评论,提到了链式调用。
- 关于如何发布npm包可以参考大佬的文章,我这里主要想说一下实现过程中遇到的问题。
一、实现效果
浏览器效果
node终端效果
二、基本使用
npm i easy-style-log
- 通过链式调用,固定最后调用的方法,执行
console.log
。也就是说最后一个方法必须是固定的。这种方法确实不优雅,当然也有其他实现方式,在文章最后我会列举链式调用的多种实现,或许有你感兴趣的。
// Es Module
import logger from "easy-style-log"
// CommonJs
// const logger = require("easy-style-log")
logger.bg("#000000").debug('data')
logger.bg("#000000").log('data')// Es Module
import logger from "easy-style-log"
// CommonJs
// const logger = require("easy-style-log")
logger.bg("#000000").debug('data')
logger.bg("#000000").log('data')
其他使用方式请参考:
三、遇到的一些困难
1. 控制台的sourcemap指向
- 这个指向总是指向包内部,导致调试并不友好
解决方式一:
- 调用
console.log
的位置,将console.log
的结果返回
代码:src/class/Log.ts
log = (...data: unknown[]) => {
// ...
return console.log(...styles, ...data);// 这里将console结果返回
};
- 这种方式解决了问题,但存在一些问题:
- 谷歌浏览器有时并不能准确显示,有时需要刷新页面才能显示正确的sourcemap,其他浏览器又是对的。具体是什么原因暂时还没有找到,知道原因的伙伴可以分享一下。
解决方式二:
- 单独增加一个调试模式,与调试方法
- 利用
console.trace
方法,它会列举出所有调用栈的信息
代码:src/class/Log.ts
log = (...data: unknown[]) => {
const styles = this.#logTemplate();
if (Log.isDebug && !isNode) {
return console.trace(...styles, ...data); // console.trace会打印出所有调用栈信息
}
return console.log(...styles, ...data);
};
debug = (...data: unknown[]) => {
const styles = this.#logTemplate(true);
if (!isNode) {
return console.trace(...styles, ...data);
}
return console.log(...styles, ...data);
};
- 这种方式也存在问题,
console.trace
默认会打开展示所有信息,如果控制台都使用了调试模式,控制台数据会很多。
目前只想到了以上两种解决方案,有其他解决方案的伙伴,欢迎讨论。
2. 调试模式下,node终端如何获取当前调用的位置
- 思路:代码抛错时,会展示所有的调用栈信息,然后通过
try...catch
就能获取到具体位置了 - 效果:
代码:src/utils/logLocation.ts
const logLocation = (line: number = 3): string => {
let location: string = "";
try {
throw new Error();
} catch (err) {
// 处理字符串
const stack: string = (<Error>err).stack || "";
const fullPath: string[] = <string[]>stack.match(/\s((.*?))\s/g);
const path = fullPath[line].replaceAll("\", "/");
location = (<string[]>path.match(/.*/(.*?))\s$/))[1];
}
return `${location} `;
};
export default logLocation;
3. node终端颜色如何控制
node
环境下的console.log
与浏览器的console
控制样式的方式并不一样。它主要利用的是ANSI Escape code
比如:
console.log('\x1B[42;31mhello world\x1B[49;39m')
\x1B[42;31m
表示ansi
开始,42
表示绿色背景色,31
表示红色文字色\x1B[49;39m
表示ansi
结束,49;39
分别代表背景色,文字色的结束- 文案写在开始和结束中间
4. 如何将hex或rgb颜色转为ansi
这部分主要参考了chalk源码。
- 具体实现代码参考:src/utils/colorToAnsi.ts
四、解决链式调用的多种方式
1. object.method1().method2().last()
这个方式就是目前项目用到的方式
思路:
- 每次执行对象方法时,都将
this
返回,既可实现链式调用 - 最后一次调用时,需要固定最后的方法,做最后的处理
伪代码:
class O{
method1(...args){
// 处理业务逻辑
console.log('method1',args)
return this
}
method2(...args){
// 处理业务逻辑
console.log('method2',args)
return this
}
last(...args){
// 这里做最后的处理
console.log('last',this,args)
}
}
// test
const o = new O()
o.method1('1').method2('2').last('last')
o.method2('2').method1('1').last('last')
2. object.method1().method2().method3()
这种方式,不需要固定方法调用的顺序,调用完成后做最后的处理。此方案在本项目的tag:v0.0.2中有使用,最后由于性能与调试问题,弃用。
思路:
- 主要利用浏览器线程的执行顺序,将需要最后处理的方法放入任务队列中,js主线程执行完毕后,才会执行任务队列中的任务
- 每次调用一个方法时,将最后处理的方法,放入任务队列中
- 在任务队列中有多个相同的任务,此时只需执行其中一个,取消其他任务即可
伪代码:
class O {
#timers = [] // 存放任务
method1(...args) {
console.log("method1", args)
this.#last() // 放入任务队列
return this
}
method2(...args) {
console.log("method2", args)
this.#last()
return this
}
method3(...args) {
console.log("method3", args)
this.#last()
return this
}
#last() {
// 放入任务队列中,也可以使用promise
const timer = setTimeout(() => {
if (this.#timers.length === 1) {
// 这里执行最后处理的逻辑
console.log('last',this)
}
clearTimeout(this.#timers.shift());
});
this.#timers.push(timer);
}
}
// test
const o = new O()
o.method1("1").method2("2")
o.method2("2").method3("3").method1("1")
3. object.params1.params2.params3()
这种方式也不需要固定调用顺序,最后一次执行函数时,就执行最后的操作。如果只是访问方法属性,则不执行最后的操作。
思路:
- 利用
Object.defineProperty
中的getter
,每次访问属性时,都将返回一个函数 - 这个函数的原型链上需要挂载这个原始对象,实现链式调用
- 每次
getter
触发时,需要把上一个函数压栈到当前这个函数的某个属性中,这样最后一次执行时,才能知道之前访问了哪些params
代码:
const o = {}
const params = ["params1", "params2", "params3", "params4"]
for (const p of params) {
Object.defineProperty(o, p, {
get() {
const func = (...args) => last(func, ...args) // 把当前函数本身传递给最后执行的函数
// 将原始对象挂载到,实现链式调用
Object.setPrototypeOf(func, o)
// 将当前参数名绑定到当前函数上
func.paramsName = p
// this指向的是上一个函数,把上一个函数存在当前函数的某个属性上
func.preFunc = this
return func
},
})
}
const last = (self, ...args) => {
// 处理最后一次调用
console.dir(self) // 当前函数上会有之前所有访问过的params信息
console.log(args)
}
// test
o.params1.params2.params3("last 1")
o.params2.params1.params4("last 2")
- 在控制台试试
如果大家还有其他链式调用的方案,欢迎在评论区讨论
项目地址:
欢迎pr,有收获的话也麻烦给个star~,有兴趣的伙伴也可以提点需求
参考文章:
转载自:https://juejin.cn/post/7255493179942993957