当前位置:嗨网首页>书籍在线阅读

05-自定义函数模块

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

20.4 自定义函数模块

最常见的模块是对外暴露一个对象,有时候也会是单个函数。另一个很常见的模式是:一个模块暴露了一个会立即被调用的函数。该函数的返回值(返回值也可能是个函数)会被使用(换句话说,开发人员并不使用模块返回的函数,而是使用函数调用后的返回值)。当需要对模块进行一定程度上的自定义,或者模块需要接受当前执行上下文中的信息的时候,通常会使用这种模式。下面来看一个真正的npm包:debug。在引入debug时,它会接受一个string参数,用来作log前缀,以便区分程序中不同地方的log。它的用法是:

const debug = require('debug')('main'); // 注意这里我们立即调用模块
                                        //返回的函数
debug("starting");                      // 在debug模式下会打印出
                                        //“main starting +0ms”
为了使用debug库中的debug功能,设置一个叫作DEBUG的环境变量。在上例中,让 `DEBUG=main` 。也可以让 `DEBUG=*` ,从而让所有debug消息都变得可用。

从这个例中可以清楚地看到,debug模块返回了一个函数(因为立即将它当做一个函数去调用了),而这个函数又返回了一个能“记住”上一个函数中字符串的函数。其实,已经把那个值“烙”在模块中了。下面来看看如何实现自己的debug模块:

let lastMessage;
module.exports = function(prefix) {
    return function(message) {
        const now = Date.now();
        const sinceLastMessage = now - (lastMessage || now);
        console.log('${prefix} ${message} +${sinceLastMessage}ms');
        lastMessage = now;
    } 
} 

这个模块输出了一个函数,它被设计成立即调用,所以prefix的值可以被合并到模块中。注意这里还有另一个值, lastMessage ,这是打印上一条消息时的时间戳,它可以用来计算消息之间的时间差。

这里就引出了一个非常重要的问题:多次引入模块的话会怎样呢?想象一下,如果把自己写的 debug module 引入两次会怎样呢?

const debug1 = require('./debug')('one');
const debug2 = require('./debug')('two');
debug1('started first debugger!')
debug2('started second debugger!')
setTimeout(function() {
    debug1('after some time...');
    debug2('what happens?');
}, 200); 

大家期望的输出可能是:

one started first debugger! +0ms
two started second debugger! +0ms
one after some time... +200ms
two what happens? +200ms

但实际上的输出是(毫秒数可能会略有偏差):

one started first debugger! +0ms
two started second debugger! +0ms
one after some time... +200ms
two what happens? +0ms

这就证明,在每个Node应用启动的时候,Node只会引入模块一次。所以即使引入了两次debug模块,Node还是会“记住”已经引入过一次,然后使用上次引入的实例。因此,即使debug1和debug2是两个独立的函数,他们依然共享相同的lastMessage引用。

Node的这种行为是一种安全且合适的行为。因为考虑到性能、内存使用和可维护性等因素,每个模块最好只被导入一次。

其实编写自定义debug模块的方式与npm中的同名模块方式一样。不过,如果真的需要包含独立时间的多个debug log时,可以把lastMessage时间戳挪到模块返回函数的函数体中,那样的话,在每次创建logger的时候它都能接收到一个全新且独立的值。