只有在 DOM 准备就绪后才开始操作它是古老的常识,我们可以确定所有元素都可用,在后 jQuery 时代,我们都为此使用
DOMContentLoaded
事件。
现在 web 组件(尤其是自治自定义元素的形式)倾向于创建自己的 HTML,通常在
connectedCallback()
生命周期方法中。
第一题:
DOMContentLoaded
与(自主)自定义元素有何关系?只有在所有组件 connectedCallbacks 完成后事件才会发生吗?如果不是,我如何确保某些代码仅在 Web 组件完成初始化后执行?
第二个问题,完全相关:
Web 组件如何与
defer
元素的script
属性相关?
我不喜欢网络组件,但我会说......一点也不喜欢。
您的组件由您的脚本定义,但在此之前,浏览器仍将像往常一样解析标记并执行所有同步脚本,并在完成时触发 DOMContentLoaded。
因此,如果您确实在 before DOMContentLoaded 事件触发之前同步定义了 CustomElements,那么您的元素
connectedCallback
将被触发(因为它不是一个事件,它是一个回调,并且被称为 synchronously)。
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 一样。
关于您的最后一个问题,正如in the docs about the
defer
attribute,具有此类属性的脚本将在before DOMContentLoaded fires.
它们在不同的轴上。
DOMContentLoaded
是关于解析初始 HTML 文档,因此是下载的原始“文件”。组件在定义时准备就绪。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 和MDN connectedCallback
connectedCallback
中,您可以将代码放在像这样的“DOMContentLoaded”事件监听器
connectedCallback() {
addEventListener('DOMContentLoaded', () => {
// Your code, such as both `this.children` and `this.shadowRoot.querySelector()`
});
}
或者,您可以使用非常冗长的 MutationObserver 和去抖功能;但后者是过度的。
对于你的第二个问题:虽然差异仍然有效,但我会避免它,以便浏览器可以尽快处理组件 DOM,而不是等到整个页面文档准备好。