这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
前言
今天要讲的库是 kleur ,号称在颜色格式化终端文本中最快的 Node.js 库,利用 ANSI 码(一种字符码)对文本进行格式化。
先来康康 kleur 的使用方式:
const { bold, green } = require('kleur');
console.log(bold().red('this is a bold red message'));
console.log(bold().italic('this is a bold italicized message'));
console.log(bold().yellow().bgRed().italic('this is a bold yellow italicized message'));
console.log(green().bold().underline('this is a bold green underlined message'));
实现思路
链式调用
- 创建可链式调用的
对象 $
$ 对象 包括多个 key 属性,其 value 值是一个初始化函数
const $ = {
enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && (
FORCE_COLOR != null && FORCE_COLOR !== '0' || isTTY
), // 是否可用
// modifiers
bold: init(1, 22),
italic: init(3, 23),
underline: init(4, 24),
// colors
red: init(31, 39),
green: init(32, 39),
yellow: init(33, 39),
// background colors
bgBlack: init(40, 49),
bgRed: init(41, 49),
};
export default $;
-
初始化链式对象方法
-
利用 ANSI 码 包裹文本即可使得打印出的文本带有某些样式
console.log(`\x1b[1m \x1b[31m hello \x1b[22m \x1b[39m`)
下面看看 init 方法主要做了什么工作。
function init(open, close) {
let blk = { //
open: `\x1b[${open}m`,
close: `\x1b[${close}m`,
rgx: new RegExp(`\x1b\[${close}m`, 'g')
};
return function (txt) { // 返回一个函数【闭包】,可接收文本
if (this !== void 0 && this.has !== void 0) { // this指向调用者
this.has.includes(open) || (this.has.push(open), this.keys.push(blk));
return txt === void 0 ? this : $.enabled ? run(this.keys, txt + '') : txt + '';
}
// 1. 没有txt,通过chain()函数链式调用
// 2. 有txt,输出txt
return txt === void 0
? chain([open], [blk])
: $.enabled ? run([blk], txt + '') : txt + '';
};
}
-
init() 接收两个数字参数:ANSI 码的起止值
-
init() 返回可接收文本参数的函数
首先在最开始创建了 blk 这个在内层函数闭包可访问的变量。
然后返回的函数中,可接收文本参数,通过 chain 函数进行链式调用。
- 链式调用函数的实现
chain
function chain(has, keys) {
let ctx = { has, keys };
ctx.bold = $.bold.bind(ctx);
ctx.italic = $.italic.bind(ctx);
ctx.underline = $.underline.bind(ctx);
ctx.red = $.red.bind(ctx);
ctx.green = $.green.bind(ctx);
ctx.yellow = $.yellow.bind(ctx);
ctx.bgBlack = $.bgBlack.bind(ctx);
ctx.bgRed = $.bgRed.bind(ctx);
return ctx;
}
- 新建 ctx 对象,用于链式调用执行。
注意:
这里不能直接返回 是
kleur库提供的,需要保持纯净,不影响其他人使用; 将 $ 的相关方法通过 bind() 绑定到 ctx 对象,改变 this 指向为 ctx 不使用 for 循环,而是挨个 bind(),避免一些不必要的属性/方法绑定造成副作用
- 执行函数的实现
run
function run(arr, str) {
let i=0, tmp, beg='', end=''; // 先声明再使用
for (; i < arr.length; i++) {
tmp = arr[i];
beg += tmp.open;
end += tmp.close;
if (str.includes(tmp.close)) {
str = str.replace(tmp.rgx, tmp.close + tmp.open);
}
}
return beg + str + end;
}
这里接受 ANSI 的数组和目标文本 str,然后一个字符串可以有多个样式 ANSI 码,循环数组取到每一项进行拼接,
最终输出 ANSI + str + ANSI 带有样式的文本。(输出不限制一定是 console.log)
总结
kleur 可借鉴的思路:
- 功能隔离,工具函数保证代码更清晰
- 链式调用,bind() 绑定新对象保证原输出对象的纯净
看到这里,相信聪明伶俐的小伙伴儿已经理解了 kleur 源码的思路,现在我们可以自己试着实现一个简单的 kleur :
const $ = {
bold: init(1, 22),
red: init(31, 39),
green: init(32, 39),
bgRed: init(41, 49),
bgYellow: init(43, 49),
}
function chain(base) {
const context = {base}
context.bold = $.bold.bind(context)
context.green = $.green.bind(context)
context.red = $.red.bind(context)
context.bgRed = $.bgRed.bind(context)
context.bgYellow = $.bgYellow.bind(context)
return context
}
function run(base, str) {
let start = '', end = ''
base.forEach(item => {
start += item.start,
end += item.end
})
return start + str + end
}
function init(start, end) {
const base = {
start: `\x1b[${start}m`,
end: `\x1b[${end}m`
}
return function (txt) {
return !txt ? chain(base) : run([base], txt + '')
}
}
console.log($.bold().green().bgRed('gyn'))