Node.js 模块之 Modules 与 Globals

后端2016-09-250 篇评论 Node.js

在浏览器中,顶级作用域就是全局作用域。也就是说在全局作用域中通过 var something 会定义一个全局可访问的变量,这个变量会附加到全局对象 window 成为它的一个属性。

Node.js 采用 CommonJS 模块加载机制,一个文件对应一个模块。顶级作用域并不是全局作用域,而是模块作用域。在一个模块被加载前,会被 Node.js 使用一个匿名函数包裹:

(function (exports, require, module, __filename, __dirname) {
    // 模块文件的代码
})

因为有了这一个匿名函数的存在,而函数内部具有单独的作用域,因而有了模块作用域一说,这也是 Node.js 不同于浏览器的一点。

模块加载

exportsmodule 对象的一个属性,用来导出作为模块的属性,可通过 exports.foo = 'bar' 这种直接给 exports 属性赋值的方式导出任意的对象作为模块的属性,并可通过 require() 获取此模块对象。想把对象作为模块本身导出时,就不能直接将 exports 重新赋值,因为这会修改 exports 变量本身,而应该将 module 对象的 exports 指向需要导出的对象,这样通过修改 exports 的引用来完成模块的导出。

使用 require.main === module 可以判断该模块是否是作为直接被 Node.js 执行的程序入口,还是作为模块被引入。

模块加载缓存

模块在第一次加载的时候会执行,并进行缓存,使得其在之后通过 require() 再次被加载的时候直接返回缓存的结果,不会再执行一次。模块是否已被缓存是通过模块文件的绝对路径来判断的,并且对路径的大小写敏感。

如果一个模块需要被多次执行,那么请将它作为一个函数导出,然后手动执行这个函数。例如:

const Expres = require('express');
const app1 = Express();
const app2 = Express();

这样就能够实现在一个进程中启动多个 Express 服务器了。

循环依赖模块的加载

当有循环依赖模块加载出现的时候,在 require() 函数返回时,这个模块可能并没有完全加载完成,例如:

// a.js

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
// b.js

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
// main.js

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

上面几段代码中,main.js 文件先后加载了 a.jsb.js 两个模块,然而 a.jsb.js 在加载期间又有相互依赖。为了避免循环依赖加载情况出现,在 a.js 模块中加载 b.js 后,但依然会先将未加载完成的 a.js 模块导出,供其它模块加载。等 b.js 模块加载完成之后,再继续 a.js 模块的加载工作。所以最终的执行结果如下:

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true

模块加载顺序

Node.js 有一些核心模块,如 httpfsos 等一些内置的模块,它们在 Node.js 编译的时候就被一同编译成了 C 语言数组的形式,在加载的时候直接通过 C++ 来调用,因此具有最高的加载优先级,总是优先被加载。

如果在 require() 中传入的模块名称以 .//../ 等相对或绝对路径开头,则从模块相应路径中加载,若此不存在此文件或目录则报错。

如果一个模块的扩展名未知,Node.js 会试图依次向其添加 .js.json.node 扩展名进行加载。

  • 对于 .js 文件,将其作为一个 JavaScript 文本文件加载。
  • 对于 .json 文件,将其作为 JavaScript 对象加载。
  • 对于 .node 文件,将其作为已编译的二进制文件加载。

模块同样可以通过目录形式来加载。如果目录中存在 package.json 文件,则以其中 main 字段中的文件作为加载入口,否则会尝试加载此目录下的 index.jsindex.node 文件作为加载入口。

如果模块不是一个核心模块,并且名称不以相对或绝对路径开头,则从当前目录下的 node_modules 目录下去寻找,若未找到则返回上一层目录的 node_modules 目录下再次寻找,直到系统根目录 /node_modules 下依旧未找到此模块时报错。

全局对象

在 Node.js 中,同样也有一些全局对象,可在模块中直接访问。除去 JavaScript 中的一些内建对象之外,还有以下对象是可以直接访问的:

  • global:全局命名空间,类似于浏览器中的 window 对象,其属性对象能够被全局直接访问。
  • __dirname:当前文件的绝对目录,非全局对象。
  • __filename:当前文件的文件名,非全局对象。
  • module:当前模块的一个引用,非全局对象。其中的 module.export 用来导出一个模块。
  • exportsmodule.exports 的引用,非全局对象。
  • require:导入模块的函数,非全局对象。
  • BufferBuffer 模块中所提供的对象,但不需要加载此模块就能直接访问。
  • console:负责向控制台打印输出。
  • process:进程对象,所有运行在 Node 环境下的程序都有一个 process 实例对象,用来提供当前 Node.js 进程信息
  • setTimeoutsetIntervalsetImmediate:设置定时器
  • clearTimeoutclearIntervalclearImmediate:取消定时器

注意:有些在模块中可以直接访问的对象或函数并不是真正的全局对象,它们是针对于每个模块的独立对象。

评论区

发表评论
用户名
(必填)
电子邮箱
(必填)
个人网站
(选填)
评论内容
Copyright © 2017 dremy.cn
皖ICP备16015002号