•
阅读 3 分钟
通过 import() 获取 JavaScript 值
import() 操作符让我们动态加载地 ECMAScript 模块,但是它们也可以取得 JavaScript 代码运行值(正像 Andrea Giammarchi 向我指出的那样),作为 eval()
的替代。这篇博客文章解释了它是如何工作的。
eval() 不支持 export 和 import
eval()
一个重要限制是它不支持诸如 export
和 import
的模块语法。
如果我们使用 import()
替换 eval()
,我们可以竟能模块代码取值,比如在这篇博客文章接下来将看到的那样。
在未来,我们能获取 Realms 那样的功能,一个功能更强大支持模块的 eval()
。
通过 import() 获取简单代码值
让我们开始使用 import()
获取一条 console.log()
输出值:
const js = `console.log('Hello everyone!');`
const encodedJs = encodeURIComponent(js)
const dataUri = `data:text/javascript;charset=utf-8,${encodedJs}`
import(dataUri)
// Output:
// 'Hello everyone!'
这里发生了什么?
- 首先我们创建了所谓的 data URI。这种 URI 的协议是
data
:URI 的其余部分编码完整的资源,而不是指向它。 在这种情况下,数据 URI 包含完整的 ECMAScript 模块 - 其内容的类型为text/javascript
。 - 然后,我们动态导入此模块并执行它。
注意:这些代码仅仅工作在网络浏览器。在 Node.js 上,import()
不支持 data URI。
访问一个可求值模块的导出(export)
通过 import()
取得的 Promise 返回的履行值是一个模块命名空间对象。这能让我们访问模块的默认导出(export default
) 和名称导出(exports)。在下面的例子中,我们访问了默认导出:
const js = `export default 'Returned value'`
const dataUri = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`
import(dataUri).then((namespaceObject) => {
console.log(namespaceObject.default === 'Returned value')
// -> true
})
通过标记模板创建 data URI
通过适用函数 esm
(我们将稍后看到其实现),我们可以上面的例子并通过标记模板创建 data URI:
const dataUri = esm`export default 'Returned value'`
import(dataUri).then((namespaceObject) => {
console.log(namespaceObject.default === 'Returned value')
// -> true
})
esm
的实现是这样的:
function esm(templateStrings, ...substitutions) {
let js = templateStrings.raw[0]
for (let i = 0; i < substitutions.length; i++) {
js += substitutions[i] + templateStrings.raw[i + 1]
}
return `data:text/javascript;base64,${btoa(js)}`
}
对于编码,我们切换 charset=utf-8
到 base64
。之间的对比:
- 源代码:
'a' < 'b'
- Data URI 1:
data:text/javascript;charset=utf-8,'a'%20%3C%20'b'
- Data URI 2:
data:text/javascript;base64,J2EnIDwgJ2In
两种编码中的每一种都有不同的利弊:
charset = utf-8
(百分比编码)的优点:- 许多源代码仍然可读。
base64
的优点:- URI 通常较短。
- 因为它不包含撇号之类的特殊字符,所以嵌套起来更容易(我们将在后面看到)。
btoa()
是一个使用 base64 编码字符串的全局工具函数。注意:
- 在 Node.js 上不可用。
- 仅仅可用于 0 到 255 范围内的 Unicode 字符。
获取一个包含导入其它模块的模块的值
通过标记模板,我们能嵌套 data URI 并对导入另一个模块 m1
的模块 m2
进行编码:
const m1 = esm`export function f() { return 'Hello!' }`
const m2 = esm`import {f} from '${m1}'; export default f()+f();`
import(m2).then(ns => console.log(ns.default === 'Hello!Hello!'))
// -> true
延伸阅读
- 维基百科上的 Data URI
- “JavaScript for impatient programmers” 中的章节 import()
- “JavaScript for impatient programmers” 中的章节 tagged templates
本文由 吳文俊 翻译,原文地址 Evaluating JavaScript code via import()
标签:
#javascript
#esnext