8

这不起作用:

<!-- 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!')
  }
})

Firefox 在线向我抛出了一个令人讨厌的异常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'). 然而显然,这不会发生。

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

有人可以解决我的困惑吗?

(如果我在模块内定义了一个自定义元素,我如何在这个模块之外公开它的 API?)

4

2 回答 2

4

问题是 a<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>
于 2019-07-11T10:32:57.567 回答
0

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

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

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

<script src="./wtf.js"></script>
于 2019-07-11T22:46:36.413 回答