专栏首页Super 前端动态执行脚本

动态执行脚本

提到动态执行脚本,大家想到的肯定是 evalnew 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 的一个好的方法。

Function

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
}

Nodejs 动态执行脚本

通过 node 的核心模块 vm 来实现。vm可以使用v8的Virtual Machine contexts动态地编译和执行代码,而代码的执行上下文是与当前进程隔离的,但是这里的隔离并不是绝对的安全,不完全等同浏览器的沙箱环境。

  1. 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 }
  2. 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 }
  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')
  4. eval() Nodejs 中同样可以使用 eval 函数,但性能和安全性有差异。请查看 https://odino.org/eval-no-more-understanding-vm-vm2-nodejs/
  5. vm2 Node.js 的高级 vm/sandbox,https://github.com/patriksimek/vm2

上下文隔离化

所有用 Node.js 所运行的 JavaScript 代码都是在一个“上下文”的作用域中被执行的。在 V8 中,一个上下文是一个执行环境,它允许分离的,无关的 JavaScript 应用在一个 V8 的单例中被运行。 必须明确地指定用于运行所有 JavaScript 代码的上下文。

vm.createContext([contextObject[, options]])

contextObject参数(如果 contextObjectundefined,则为新创建的对象)在内部与 V8 上下文的新实例相关联。 该 V8 上下文提供了使用 vm 模块的方法运行的 code 以及可在其中运行的隔离的全局环境。

使用场景

动态执行字符串代码。vue ssr 中是通过 runInNewContext 实现的( Vue SSR 指南)。

参考地址

  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval
  • http://nodejs.cn/api/vm.html
  • https://ssr.vuejs.org/zh/api/#runinnewcontext
  • https://odino.org/eval-no-more-understanding-vm-vm2-nodejs/
  • https://github.com/patriksimek/vm2

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue数据代理检测(源码)

    阅读源码通常是枯燥无味的,类似 Vue 这种框架级的,代码量更是巨大;且各个实现之间关联性很大,跟踪源码非常跳跃,看完后总是稀里糊涂。今天,从一个常见的错误说起...

    奋飛
  • JavaScript数据结构-字典

    字典类(Dictionary)基于Object。在《数据结构与算法JavaScript描述》书中“字典”采用了数组存储数据,不仅让阅读者很难理解,而且也没有实现...

    奋飛
  • JavaScript事件驱动机制&定时器机制

    在浏览器中,事件作为一个极为重要的机制,给予JavaScript响应用户操作与DOM变化的能力;在NodeJS中,异步事件驱动模型则是提高并发能力的基础。

    奋飛
  • 从代码中就可以看出一个程序员的性格?

    之前有一个领导说过,自己写的代码就像自己的孩子,要对他负责,曾经一度认为他说的是真理,后来才发现,实际工作中是特别的复杂的,自己的“孩子”到最后还是会交给其他人...

    一墨编程学习
  • java8的parallelStream和strem

    java8除了新增stream,还提供了parallel stream-多线程版的stream,parallel stream的优势是:充分利用多线程,提高程序...

    似水的流年
  • 编程不息,Bug 不止

    今天不想聊别的,就想聊点 Bug,是不是感觉我有点傲娇呢?昨天大家的留言我都一一仔细看完了,看完之后,就想到了一句话:生命不息,坎坷不止。2016年大家真的是被...

    非著名程序员
  • 到底什么是开源协议和ARM授权模式?

    License是软件的授权许可,里面详尽表述了你获得代码后拥有的权利,可以对别人的作品进行何种操作,何种操作又是被禁止的。软件协议可分为开源和商业两类,对于商业...

    刘盼
  • 淘课之家 ~ 什么是低代码(Low-Code)?

    虽然程序员真的不喜欢重复自己,但冗余也是一种必要的容错手段,好东西真的不容错过:欢迎各位技术同路人加入淘课之家原生应用研发平台EMAS团队,我们专注于广泛的云原...

    菜鸟学院
  • 程序员的日常:为了写一段代码......

    @TMM-eng:本来想周末看看代码的,结果呢,代码忘了往远程推了,在家用自己的电脑download的时候傻眼了,不是最新版本的,好嗨哦

    极乐君
  • 从混沌到有序的远程办公进阶之路

    抗击疫情,腾讯云在行动。为保证疫情之下员工的安全,国内启动了有史以来最大规模的远程办公。由于来的突然,很多企业和个人无论是从心态还是基础设施层面都没有做好远程办...

    用户7118204

扫码关注云+社区

领取腾讯云代金券