一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 6 天,点击查看活动详情。
TC39
关于 TC39 的介绍可以看前一篇文章,这里不再赘述。
ShadowRealms
今天要了解的新特性是 Shadowrealm,这项特性提案时间为 2021 年 12 月,不到一年的时间已经进展到 stage-3 阶段,目前组委会已经在在做它的功能实现,有望在下个版本推出。
realm 的意思是领域,shadowrealm 的目的是提供一种独立的 JavaScript 运行环境。在当前 ECMAScript 中能实现 js 独立运行环境方式有以下几种:
- 1.iframe:每个 iframe 都是独立的运行环境,但 iframe 需要在页面创建元素,而且只能在浏览器端使用,且有较高的维护成本;
- 2.eval 或 Function:功能太过单一,需要更丰富完整的实现
- 3.nodejs 的 vm 模块:是隔离 js 运行环境的较好的方案,所以当前提案的实现也是参考了 vm 模块
- 4.Web Workers:同样不是 ECMAScript 自有能力,单独引进来使用或许成本太高
总之是目前实现 js 独立运行环境能力太过薄弱,需要打造成熟的语法体系,就有了 ShadowRealms
基本使用
ShadowRealm 的类型签名如下:
declare class ShadowRealm {
constructor();
importValue(specifier: string, bindingName: string):
Promise<PrimitiveValueOrCallable>;
evaluate(sourceText: string): PrimitiveValueOrCallable;
}
该类包含两个主要方法:
- importValue:接收两个参数,返回一个是原始或可调用的 Promise 对象
- evaluate:接收一个参数,返回原始值或可调用值
最基本的使用效果如下:
const sr = new ShadowRealm()
globalThis.a = 999
sr.evaluate("globalThis.a = 1000")
console.log(globalThis.a) // 999
可以看到 ShadowRealm 对象里的操作执行并不会影响外部全局环境的内容;接下来看这个两个方法更多运用场景。
importValue
importValue 基本使用如下:
const red = new ShadowRealm();
const redAdd = await red.importValue('./out.js', 'add');
let result = redAdd(2, 3);
console.assert(result === 5); // yields true
// out.js
export function add(...values) {
return values.reduce((prev, value) => prev + value);
}
importValue 可以直接引入一个外部模块,是遵循 ES6 模块规范的。特别要注意的是这里的 add 必须是一个可调用的内容或者原始类型数据。另外 importValue 返回的是一个 promise,异步执行并返回内容。所以,更灵活的用法如下:
let myRealm = new ShadowRealm();
const { runFunction, testFunction, createFunction } = await
myRealm.importValue('./function-script.js');
// 或者
const [ runFunction, testFunction, createFunction ] = await Promise.all([
myRealm.importValue('./file-one.js', 'runFunction'),
myRealm.importValue('./file-two.js', 'testFunction'),
myRealm.importValue('./file-three.js', 'createFunction'),
]);
let fileAnalysis = runFunction();
或者像正常 Promise 一样使用:
window.greet = 'hello';
let myRealm = new ShadowRealm();
myRealm.importValue('someFile.js', 'fn').then((fn) => {
console.log(fn("name")); // 调用 fn 方法
console.log(window.greet); // 内部没有 greet ,打印 undefined
})
evaluate
evaluate 用法和现有的 eval 差不多,就是参数只支持基本数据类型或可调用值:
globalThis.realm = 'global realm';
const sr = new ShadowRealm();
sr.evaluate(`globalThis.realm = 'inner realm'`);
const wrappedFunc = sr.evaluate(`() => globalThis.realm`);
console.assert(wrappedFunc() === 'inner realm'); // true
这里 wrappedFunc 执行后返回的是 ShadowRealm 里的 globalThis.realm,是 evaluate 里赋值的内容:inner realm。
总结和思考
看到这个提案第一时间想到的是 WebComponent 的 shadow-dom,可以理解为是偏向 HTML,CSS 结构隔离的,具体内容可参见之前的博文一文吃透 WebComponents。ShadowRealms 旨在做 js 运行环境的隔离,那和时下火热的微前端内容息息相关,比如 single-spa 内部没有做子应用的 js 沙箱隔离,当前使用的方案一般是借用 proxy 做实现:
class SandboxWin {
constructor(){
let rawWin = globalThis
let fakeWin = {}
this.proxy = new Proxy(fakeWin, {
get(target,key){
return fakeWin[key] || target[key]
},
set(target,key, val){
target[key] = val
return val
}
})
return this.proxy
}
}
let sandBox1 = new SandboxWin()
window.a = 1;
(window=>{
window.a = 'sandBox1'
})(sandBox1.proxy)
console.log(window.a) // 1
未来要是 ShadowRealms 实施落地,js 沙箱方案就可以变得更加简洁优雅了,期待它的到来。
以上,感谢阅读。更多交流想法请评论区留言。