在解析v-if
和v-for
等指令时我们会看到通过evaluate
执行指令值中的JavaScript表达式,而且能够读取当前作用域上的属性。而evaluate
的实现如下:
const evalCache: Record<string, Function> = Object.create(null)
export const evaluate = (scope: any, exp: string, el?: Node) =>
execute(scope, `return(${exp})`, el)
export const execute = (scope: any, exp: string, el?: Node) => {
const fn = evalCache[exp] || (evalCache[exp] = toFunction(exp))
try {
return fn(scope, el)
} catch (e) {
if (import.meta.env.DEV) {
console.warn(`Error when evaluating expression "${exp}":`)
}
console.error(e)
}
}
const toFunction = (exp: string): Function => {
try {
return new Function(`$data`, `$el`, `with($data){${exp}}`)
} catch (e) {
console.error(`${(e as Error).message} in expression: ${exp}`)
return () => {}
}
}
简化为如下
export const evaluate = (scope: any, exp: string, el?: Node) => {
return (new Function(`$data`, `$el`, `with($data){return(${exp})}`))(scope, el)
}
而这里就是通过with
+new Function
构建一个简单的沙箱,为v-if
和v-for
指令提供一个可控的JavaScript表达式的执行环境。
什么是沙箱
沙箱(Sandbox)作为一种安全机制,用于提供一个独立的可控的执行环境供未经测试或不受信任的程序运行,并且程序运行不会影响污染外部程序的执行环境(如篡改/劫持window对象及其属性),也不会影响外部程序的运行。
与此同时,沙箱和外部程序可以通过预期的方式进行通信。
更细化的功能就是:
- 拥有独立的全局作用域和全局对象(
window
) - 沙箱提供启动、暂停、恢复和停机功能
- 多台沙箱支持并行运行
- 沙箱和主环境、沙箱和沙箱之间可实现安全通信
原生沙箱-iframe
iframe
拥有独立的browser context,不单单提供独立的JavaScript执行环境,甚至还拥有独立的HTML和CSS命名空间。
通过将iframe
的src
设置为about:blank
即保证同源且不会发生资源加载,那么就可以通过iframe.contentWindow
获取与主环境独立的window对象作为沙箱的全局对象,并通过with
将全局对象转换为全局作用域。
而iframe
的缺点:
- 若我们只需要一个独立的JavaScript执行环境,那么其它特性则不仅仅是累赘,还会带来不必要的性能开销。而且
iframe
会导致主视窗的onload事件延迟执行; - 内部程序可以访问浏览器所有API,我们无法控制白名单。(这个可以通过Proxy处理)