30 行代码实现 JS 沙箱

30 行代码实现 JS 沙箱
最新回答
人潮拥挤我该远离

2022-12-18 11:47:50

沙箱

含义:

应用:

在JavaScript中,不同作用域可以通过作用域链进行访问(如闭包),而且通过这种方式,所有作用域都能访问到全局对象。

要实现沙箱的隔离效果,需要把代码运行在一个独立的作用域,限制它对其他作用域以及全局对象的访问。

例子如下,在沙箱内变量赋值不会影响沙箱外的对象,这是createSandbox函数需要实现的基本功能。

一、代码执行

在JavaScript中,动态执行代码的方法有Function和eval。

二者对比:

代码中永远不要使用eval

选择Function函数来动态执行代码后,我们可以得出以下实现:

二、作用域固定

虽然使用Function可以让沙箱代码固定在全局作用域中执行,但我们还是需要限制代码访问全局对象。

我们可以使用Object.create()来创建一个全局对象的副本,然后使用with让沙箱代码访问这个副本对象,从而达到隔离的效果。

with语句可以将某个对象添加到作用域链的顶部。

三、全局对象代理

使用with的方式还存在一个问题,就是当访问全局对象不存在的字段时,还是能够访问到全局对象,比如新增字段。

使用Proxy代理with对象中的has方法,让每次判断都返回true,这样就能真正阻断代码通过作用域链访问全局对象。

with语句是通过in运算符来判定访问的字段是否在对象中,从而决定是否继续通过作用域链往上找。

四、特殊处理

上述代码虽然实现了基本的沙箱功能,但显然还存在一些问题:

浏览器还会对一些内置函数进行保护,比如alert和setTimeout等,这些函数必须运行在window作用域下。需要把函数的作用域修改回window。

这些函数都有个特点就是都是非构造函数,不能new,没有prototype属性,我们可以用这个特点来进行过滤。

五、最终代码

最终createSandbox函数的实现如下:

六、使用效果

可以在同一页面中同时运行vue2和vue3的代码,互不冲突。

demo:codesandbox.io/s/clever...

其他存在问题(暂时无解)冷知识Symbol.unscopables

这两个例子主要的变化是在原型链上增加了一个属性,导致with语句可以成功从对象中查找到字段,实际输出的是props.props.length,导致输出和预期变得不一样。

可以想想ES6对数组原型新增了多少方法,每个方法都可能导致这个问题。虽然这个代码看起来写法比较有歧义,但确实出现在了当时比较流行的框架EXT中。为了兼容这种情况,TC39加了一条规则。

Symbol.unscopables的引入,唯一作用就是解决with执行环境下的历史问题。TC39把ES6以后新加数组原型链方法全部加到Array.prototype[Symbol.unscopables]中,包括includes、keys和values等等。