提到动态执行脚本,大家想到的肯定是 eval 或 new Function(),在 nodejs 中有专属的 vm 模块,可以完成相应的 sandbox 作用。
eval()函数会将传入的字符串当做 JavaScript 代码进行执行,返回字符串中代码的返回值;如果参数不是字符串将原封不动返回。
如果你间接的使用 eval(),比如通过一个引用来调用它,而不是直接的调用 eval。从 ECMAScript 5 起,它工作在全局作用域下,而不是局部作用域中。
function test() {
let x = 2, y = 4;
console.log(eval('x + y')); // 直接调用,使用本地作用域,结果是 6
let geval = eval; // 等价于在全局作用域调用
console.log(geval('x + y')); // 间接调用,使用全局作用域,throws ReferenceError 因为`x`未定义
(0, eval)('x + y'); // 另一个间接调用的例子
}eval 中函数作为字符串被定义需要“(”和“)”作为前缀和后缀
let fctStr1 = 'function a() {}'
let fctStr2 = '(function a() {})'
let fct1 = eval(fctStr1) // 返回undefined
let fct2 = eval(fctStr2) // 返回一个函数MDN 建议永远不要使用 eval
eval() 使用与调用者相同的权限执行代码。如果你用 eval() 运行的字符串代码被恶意方(不怀好意的人)修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。更重要的是,第三方代码可以看到某一个 eval() 被调用时的作用域,这也有可能导致一些不同方式的攻击。
eval() 通常比其他替代方法更慢,因为它必须调用 JS 解释器,而许多其他结构则可被现代 JS 引擎进行优化。此外,现代JavaScript解释器将javascript转换为机器代码。 这意味着任何变量命名的概念都会被删除。 因此,任意一个eval的使用都会强制浏览器进行冗长的变量名称查找,以确定变量在机器代码中的位置并设置其值。
Function 是替代 eval 的一个好的方法。
new Function ([arg1[, arg2[, ...argN]],] functionBody)每个 JavaScript 函数实际上都是一个 Function 对象。运行 (function(){}).constructor === Function // true 便可以得到这个结论。
与 eval 不同的是,Function 创建的函数只能在全局作用域中运行。
function test() {
let x = 2, y = 4;
console.log(new Function('return x + y')()); // 直接调用,使用全局作用域,throws ReferenceError
}通过 node 的核心模块 vm 来实现。vm可以使用v8的Virtual Machine contexts动态地编译和执行代码,而代码的执行上下文是与当前进程隔离的,但是这里的隔离并不是绝对的安全,不完全等同浏览器的沙箱环境。
vm.runInContext(code, contextifiedObject[, options])
在指定的 contextifiedObject 的上下文里执行它并返回其结果。 被执行的代码无法获取本地作用域。 contextifiedObject 必须是事先被 vm.createContext() 方法上下文隔离化过的对象。
const vm = require('vm') const contextObject = { a: 1 } vm.createContext(contextObject) const result = vm.runInContext('a += 1; b = 3', contextObject) console.log(result) // 3 { a: 2, b: 3 }
vm.runInNewContext(code[, contextObject[, options]])
给指定的 contextObject(若为 undefined,则会新建一个contextObject)提供一个隔离的上下文, 再在此上下文中执行编译的 code,最后返回结果。 运行中的代码无法获取本地作用域。
const vm = require('vm') const result = vm.runInNewContext('a += 1; b = 3', {a: 1}) console.log(result) // 3 { a: 2, b: 3 }
vm.runInThisContext(code[, options])
在当前的 global 对象的上下文中编译并执行 code,最后返回结果。 运行中的代码无法获取本地作用域,但可以获取当前的 global 对象。
global.a = 1 const result = vm.runInThisContext('a += 1') console.log(result)
vm.runInThisContext() 更像是间接的执行 eval(), 就像 (0,eval)('code')。
eval()
Nodejs 中同样可以使用 eval 函数,但性能和安全性有差异。请查看 https://odino.org/eval-no-more-understanding-vm-vm2-nodejs/
所有用 Node.js 所运行的 JavaScript 代码都是在一个“上下文”的作用域中被执行的。在 V8 中,一个上下文是一个执行环境,它允许分离的,无关的 JavaScript 应用在一个 V8 的单例中被运行。 必须明确地指定用于运行所有 JavaScript 代码的上下文。
vm.createContext([contextObject[, options]])contextObject参数(如果 contextObject 为 undefined,则为新创建的对象)在内部与 V8 上下文的新实例相关联。 该 V8 上下文提供了使用 vm 模块的方法运行的 code 以及可在其中运行的隔离的全局环境。
动态执行字符串代码。vue ssr 中是通过 runInNewContext 实现的( Vue SSR 指南)。