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

gaa*_*kam 7 javascript scope ecmascript-6 custom-element es6-modules

这不起作用:

<!-- 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>
Run Code Online (Sandbox Code Playgroud)

 

// wtf.js
customElements.define('my-element', class extends HTMLElement {
  constructor() {
    super()
  }

  callMe() {
    window.alert('I am called!')
  }
})
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

是的,我知道模块中定义的内容仅限于此模块。但是,在我看来,这甚至还不是一个范围界定的问题。例如,如果我在模块中执行以下操作:

function callMe() {/*blah blah */}

window.callMe = callMe
Run Code Online (Sandbox Code Playgroud)

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

据我所知,在我的用例中应该发生同样的事情。即使我callMe在作用域范围内的类中进行了定义,该类方法也应该可以在模块外部访问,这是因为该方法是该类对象的一个​​属性,该属性通过调用公开document.createElement('my-element')。但是显然,这不会发生。

这对我来说真的很奇怪。似乎模块通过纠缠与类型无关的函数return(!!)来强制执行作用域-因此,在这种情况下,就像模块神奇地强制document.createElement将其返回的对象强制转换为继承层次结构(HTMLElement)?!?!这对我来说真是令人难以置信。

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

(并且,如果我在模块内部定义了自定义元素,如何将其API暴露在此模块之外?)

Ber*_*rgi 4

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

即使我在模块范围内的类中定义了 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>
Run Code Online (Sandbox Code Playgroud)