nodejs有一个叫vm的模块,这是用来运行代码的模块。
怎么说呢,我们知道浏览器只要你将代码放在script标签中,或script src远程引用它,代码就会执行,这些代码会执行一些我们没有定义但会预先传入的对象或方法,比如window, document, location, fetch等等。我们知道其实document, location, fetch这些东西都在window上,window是一个上下文。 在iframe中也有自己的window,各自独立。而在nodejs,怎么形成这个"window"呢,这就需要vm来提供。
nodejs有读取文件的能力 ,我们将一个index.js读出来,其实也只是一段文本,不会发生更大的用处。但是里面是我们的代码。要运行它们,以前我们知道用eval, new Function, 但它们不会明确指定一个上下文(eval根据调用的方式,会分为全局eval与局部eval)。
var globalVar = 11
eval('globalVar *= 2')
console.log(globalVar)
相对而言,new Function则比较麻烦
var globalVar = 11
console.log(globalVar)
var fn = new Function("globalVar", 'globalVar *= 2; return globalVar')
var a = fn(globalVar)
console.log(a) //22
new Function 是比较密封,数据太多就麻烦了。而eval则将外面的所有变量都寄及进去,太过evil。
但我们看到nodejs通过require也能运行其他模块啊。这是因为它们用到vm中的方法。
vm.runInContext(code, contextifiedObject[, options])
,它相当于Function,第二个参数sandbox相当于Function的所有传参,当我们在函数価修改sandbox的成员,那么直接能在外面接收到。它必须与vm.createContext 一起使用。
const vm = require('vm');
const sandbox = {
animal: 'cat',
count: 2,
globalVar: 99
};
vm.createContext(sandbox)
//必须传入第二个参数,sandbox或context
vm.runInContext('globalVar *= 2', sandbox);
console.log(sandbox)
vm.runInThisContext(code[, options])
, 此方法不能指定上下文,因为它的上下文就是它当前所在的上下文。它里面的变量只能是外面的全局变量与方法,如console, process,global。
const vm = require('vm');
globalVar = 13 //全局的
vm.runInThisContext('globalVar *= 2;console.log("====")');
console.log(globalVar)
var aaa = 11;//本地
vm.runInThisContext('aaa *= 2');
console.log(aaa)
在nodejs 9.10之前,module.js模块, require方法的实现就是使用 runInThisContext 来运行代码。
https://github.com/nodejs/node/blob/v9.10.0/lib/module.js#L613github.comvm.runInNewContext(code[, contextObject[, options]])
, 用法类似 runInContext,但是更加严格,只接受第二个对象传入的变量,外面的全局对象与方法一概不支持,连console.log也不行。
const vm = require('vm');
var sandbox = {
globalVar: 11,
filename: ""
}
//必须传入第二个参数,sandbox或context
vm.runInNewContext('globalVar *= 2; ', sandbox);
console.log(sandbox)
这是一个完美的沙箱。
vm.compileFunction(code[, params[, options]])
, 这个除了最后一个参数,真的和new Function没什么两样。
最后解释一下options参数, 它有用的参数有几个:filename,当我们用fs.readFile(path)得到文本, 我们可以将path当作filename值,有利于我们跟踪错误; timeout, 指定执行时间,防止死循环这类特殊情况; contextName, 为上下文添加一个名字,方便调试.
另外,我们的代码也可以用 new vm.Script 包装起来,再执行上面的方法。
下面是一个性能分析,创建一个沙箱上下文是比较耗时,与 new Function没法比。
var vm = require('vm'),
code = 'var square = n * n;',
script = vm.createScript(code),
sandbox;
n = 5;
sandbox = { n: n };
benchmark = function (title, funk) {
var end, i, start;
start = new Date;
for (i = 0; i < 5000; i++) {
funk();
}
end = new Date;
console.log(title + ': ' + (end - start) + 'ms');
}
var ctx = vm.createContext(sandbox);
benchmark('vm.runInThisContext', function () { vm.runInThisContext(code); });
benchmark('vm.runInNewContext', function () { vm.runInNewContext(code, sandbox); });
benchmark('script.runInThisContext', function () { script.runInThisContext(); });
benchmark('script.runInNewContext', function () { script.runInNewContext(sandbox); });
benchmark('script.runInContext', function () { script.runInContext(ctx); });
benchmark('vm.compileFunction', function () { vm.compileFunction(code, ['n'])(n); });
benchmark('new Function', function () { new Function('n', code)(n); });