DOMContentLoaded与Web组件的关系如何



只有在DOM准备好后才开始操作DOM是一种古老的常识,我们可以确定所有元素都可用,在后jQuery时代,我们都使用DOMContentLoaded事件来实现这一点。

现在,web组件(尤其是以自主自定义元素的形式(倾向于创建自己的HTML,通常采用connectedCallback()生命周期方法。

第一个问题:

DOMContentLoaded与(自主(自定义元素有何关联?只有在所有连接的组件回调完成后,事件才会发生吗?如果没有,我如何确保某些代码只在web组件完成初始化后执行?

第二个问题,完全相关:

web组件如何与script元素的defer属性相关?

我不喜欢web组件,但我想说。。。一点也不。

您的组件是由脚本定义的,但在此之前,浏览器仍将像往常一样解析标记并执行所有同步脚本,并在完成后激发DOMContentLoaded。

因此,如果在触发DOMContentLoaded事件之前同步定义CustomElements,则元素connectedCallback将被触发(因为它不是事件,所以它是回调,并被同步调用(。

if (window.customElements) {
addEventListener('DOMContentLoaded', e => console.log('DOM loaded'));
class MyCustom extends HTMLElement {
connectedCallback() {
console.log('Custom element added to page.');
}
}
customElements.define('my-custom', MyCustom);
console.log('Just defined my custom element')
} else {
console.log("your browser doesn't have native support");
}
<my-custom></my-custom>

但是,如果您确实在等待DOMContentLoaded事件,那么。。。回调将在之后触发。

if (window.customElements) {
addEventListener('DOMContentLoaded', e => console.log('DOM loaded'));
class MyCustom extends HTMLElement {
connectedCallback() {
console.log('Custom element added to page.');
}
}
setTimeout(()=> customElements.define('my-custom', MyCustom), 2000);
} else {
console.log("your browser doesn't have native support");
}
<my-custom></my-custom>

但是,DOMContentLoaded绝不会等待所有脚本的同步执行结束,就像你没有任何CustomElement一样。


关于您的最后一个问题,正如关于defer属性的文档中所说,在触发DOMContentLoaded之前,具有此类属性的脚本将被解析

它们位于不同的轴上。DOMContentLoaded是关于解析初始HTML文档,因此是下载的原始"文件"。组件在定义时已准备就绪
我也不熟悉这个主题,所以只是修改了MDN示例,通过按下按钮将这两个事件完全解耦。我假设新组件的定义可以在任何时候发生,事实就是这样
MDN示例位于GitHub上,链接自CustomElementRegistry.define(以及其他各种页面(。他们在GitHub IO上也有一个实时变体,但当然这只是最初的例子
把完整的例子放在这里感觉没有希望,所以这只是修改后的HTML:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pop-up info box — web components</title>
</head>
<body id="body">
<h1>Pop-up info widget - web components</h1>
<form>
<div>
<label for="cvc">Enter your CVC <popup-info img="img/alt.png" text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></label>
<input type="text" id="cvc">
</div>
</form>
<!--<script src="main.js"></script>-->
<script>
function test(){
var x=document.createElement("script");
x.src="main.js";
document.getElementById("body").appendChild(x);
}
customElements.whenDefined("popup-info").then(function(){alert("popup-info");});
document.addEventListener("DOMContentLoaded",function(){alert("DOMContentLoaded")});
</script>
<button onclick="test()">Test</button>
</body>
</html>

所以main.js不是自动加载的,只是在按下按钮后,它仍然有效。"DOMContentLoaded"弹出窗口已经发生了很长一段时间。但是,有一个CustomElementRegistry.whenDefined()是耐心的,并且只有在定义了自定义元素之后才会触发。我认为这就是您可以使用的,也许可以从DOMContentLoaded订阅它,这样您的最终事件就可以保证在DOM和自定义元素都准备好时发生。不利的一面是,您必须知道您正在等待的自定义元素的名称。(未经测试的假设,但基于https://html.spec.whatwg.org/multipage/scripting.html可能使whenDefined()发生在DOMContentLoaded之前。顺便说一句:图表还显示了defer的作用,所以如果你把whenDefined()放在一个延迟的脚本中,回调将在DOMContentLoaded之后发生,或者至少我认为这是

对于您的第一个问题:不要担心外部页面,只关心组件本身。然而,由于组件的嵌套内容和shadowDOM准备就绪时没有可用的方法(请参阅WICG Web components Issue#809和MDNconnectedCallback(,因此在connectedCallback中,您可以将代码放入";DOMContentLoaded";类似于的事件侦听器

connectedCallback() {
addEventListener('DOMContentLoaded', () => {
// Your code, such as both `this.children` and `this.shadowRoot.querySelector()`
});
}

或者,您可以使用非常详细的MutationObserver和debounce函数;但后者是过分的。

对于您的第二个问题:尽管差异仍然有效,但我会避免它,以便浏览器能够尽快处理组件DOM,而不是等到整个页面文档准备好。

最新更新