Node.js CommonJS 实现与模块的作用域

2 年前

本文源自我在 为什么 Node.js 不给每一个.js文件以独立的上下文来避免作用域被污染? 问题下的回答。希望可以和大家一起探讨一下这个问题。

回答这个问题,要从 Node.js 对 CommonJS 的实现说起。上 Node 的源码 https://github.com/nodejs/node/blob/master/lib/module.js#L556

var wrapper = Module.wrap(content);

var compiledWrapper = vm.runInThisContext(wrapper, {
  filename: filename,
  lineOffset: 0,
  displayErrors: true
});

// ...

var result = compiledWrapper.call(this.exports, this.exports, require, this,
  filename, dirname);

关键代码就这么几行。

content 可以认为是你的 .js 文件源码,例如,我们简单点:

'console.log(module)'

Module.wrap(content) 后:

'(function (exports, require, module, __filename, __dirname) { console.log(module)\n});'

注意:上面是字符串操作。

vm.runInThisContext 后:

[Function]

将上面的字符串输出变成了可执行的 JS 函数。实际上这个函数就是:

function(exports, require, module, __filename, __dirname) {
  console.log(module)
});

最后执行这个函数,也就是 compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname) 就是执行了这个模块。

以上,就是 Node.js 对一个文本的 .js 模块转换成一个可使用的 JS 模块的大致过程。

好了,回答题主问题,显然,.js 文件的代码都是包裹在一个函数里执行的,并不会产生作用域污染。

我们再追问一下,如果你不小心没有写var,定义了全局变量怎么办?,例如一不小心写了这行代码:

globalVar = 1

包裹之后变成了:

function(exports, require, module, __filename, __dirname) {
  globalVar = 1
});

那这个 globalVar 是啥样子?这其实是由 vm.runInThisContext 决定的,查看 Node.js 的文档

vm.runInThisContext() compiles code, runs it within the context of the current global and returns the result. Running code does not have access to local scope, but does have access to the current global object.

  1. vm.runInThisContext 使得包裹函数执行时无法影响本地作用域;
  2. 但 global 对象是可以访问的,因此 globalVar = 1 等价于 global.globalVar = 1

如何避免这种对全局作用域的污染呢?

'use strict';
globalVar = 1

添加 'use strict';,禁止这样意外创建全局变量,代码执行时将抛出 globalVar 未定义的错误。

更准确地回答题主的问题:Node.js 模块正常情况对作用域不会造成污染,意外创建全局变量是一种例外,可以采用严格模式来避免。

0
推荐阅读