JS 模块如何防止内部定义的自定义元素公开其 API?



这不起作用:

<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
<script type="module" src="./wtf.js"></script>
</head>
<body>
<script>
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>

 

// wtf.js
customElements.define('my-element', class extends HTMLElement {
constructor() {
super()
}
callMe() {
window.alert('I am called!')
}
})

火狐在第myElement.callMe()行上给我抛出了一个令人讨厌的异常。显然,"myElement.callMe is not a function"。

我很困惑为什么会这样?据我了解,一旦我键入const myElement = document.createElement('my-element'),我就会收到一个对象,其类型不是泛型HTMLElement而是我编写的类中扩展HTMLElement的对象!而这个类暴露了callMe.

我已经确认我对模块的使用似乎是这里的罪魁祸首。此代码按预期工作:

<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super()
}
callMe() {
window.alert('I am called!')
}
})
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>

是的,我知道模块中定义的内容的作用域在此模块范围内。但在这里,它甚至似乎(对我来说)不是一个范围界定问题。例如,如果我在模块内做这样的事情:

function callMe() {/*blah blah */}
window.callMe = callMe

那么无论如何,我将能够在模块外部使用callMe,因为模块通过export以外的其他方式公开了此函数(这次是通过将其分配给全局window对象)。

据我了解,在我的用例中也应该发生同样的情况。即使我在限定为模块的类中定义了callMe,这个类方法也应该可以在模块外部访问,因为它是通过调用document.createElement('my-element')公开的此类对象的属性。然而,显然,这并没有发生。

这对我来说真的很奇怪。几乎看起来好像模块通过纠缠不相关的类型来强制执行其范围 return(!!) - 所以在这种情况下,就好像模块神奇地强制document.createElement在继承层次结构中强制它返回的对象(HTMLElement)?!?!这对我来说是令人兴奋的。

有人可以消除我的困惑吗?

(如果我在模块中定义一个自定义元素,我如何在此模块外部公开其 API?

问题是<script type="module">隐式具有defer属性,因此它不会立即运行。

即使我在作用域为模块的类中定义了 callMe,此类方法也应该可以在模块外部访问

是的,它是。问题只是它是异步定义的:-)要使用模块中的内容,您应该显式import该模块来声明依赖项,从而确保以正确的顺序对其进行评估。如果您的全局脚本以某种方式defer红色,它也将起作用。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
<script type="module" src="./wtf.js"></script>
</head>
<body>
<script type="module">
import './wtf.js';
//  ^^^^^^^^^^^^^^^^^^
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>

您也可以等待 window.onload 事件执行内联脚本:

document.onload = () => {
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
}

或者,您可以使用经典<script>加载来确保同步加载自定义元素:

<script src="./wtf.js"></script>

最新更新