connectedCallbackの呼び出される順番を見てみる
Web Componentsの仕組みを深堀したくて、試してみる。
試してみたブラウザはChrome 114。
以下のようなHTMLファイルを用意して、ブラウザで開いてみる。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>sample</title>
<script>
function log(message) {
document.getElementById("log").innerHTML += message + "<br/>";
}
class SampleElement extends HTMLElement {
connectedCallback () {
log(`connected: ${this.id}`);
}
}
customElements.define("sample-elem", SampleElement);
</script>
</head>
<body>
<div id="log"></div>
<sample-elem id="sample-1"></sample-elem>
<script>
log("last of body");
</script>
</body>
</html>
ログをコピペしやすいように、開発者ツールのコンソールではなく <div id="log"></div>
にログ出力するようにしている。
上のHTMLだと、以下のような表示になる。
connected: sample-1
last of body
HTMLに直接書いたElementは、そのHTMLコード断片を解析したそばから connectedCallback
が呼び出される。
クラス定義を <script defer>
にしてみる。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>sample</title>
<script>
function log(message) {
document.getElementById("log").innerHTML += message + "<br/>";
}
</script>
<script defer>
class SampleElement extends HTMLElement {
connectedCallback () {
log(`connected: ${this.id}`);
}
}
customElements.define("sample-elem", SampleElement);
</script>
</head>
<body>
<div id="log"></div>
<sample-elem id="sample-1"></sample-elem>
<script>
log("last of body");
</script>
</body>
</html>
connected: sample-1
last of body
実行順序が変わらなかった。
This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.
src
がないdefer
は無意味らしい。
クラス定義を <script type="module">
にしてみる。
last of body
connected: sample-1
type="module"
ならば src
がなくてもdefer
の扱いになるようだ。
customElements.define
がDOMの解析が終わった後でも、 connectedCallback
が後から呼び出される。
<sample-elem id="sample-1">
<sample-elem id="sample-2"></sample-elem>
</sample-elem>
要素を入れ子に2つにすると、こうなる。
last of body
connected: sample-1
connected: sample-2
connectedCallback
は親要素から順に呼ばれるようだ。
要素をHTMLに静的に書くのではなく、JavaScriptから動的に挿入してみる。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>sample</title>
<script>
function log(message) {
document.getElementById("log").innerHTML += message + "<br/>";
}
</script>
<script type="module">
class SampleElement extends HTMLElement {
connectedCallback () {
log(`connected: ${this.id}`);
}
}
customElements.define("sample-elem", SampleElement);
log("before createElement");
const sampleElement1 = document.createElement("sample-elem");
sampleElement1.id = "sample-1";
log("before append");
document.querySelector("body").append(sampleElement1);
log("after append");
</script>
</head>
<body>
<div id="log"></div>
<script>
log("last of body");
</script>
</body>
</html>
last of body
before createElement
before append
connected: sample-1
after append
connectedCallback
は、createElement
よりも後で、append
の直前に呼び出される。
const sampleElement1 = document.createElement("sample-elem");
sampleElement1.id = "sample-1";
document.querySelector("body").appendChild(sampleElement1);
const sampleElement2 = document.createElement("sample-elem");
sampleElement2.id = "sample-2";
document.querySelector("#sample-1").appendChild(sampleElement2);
last of body
connected: sample-1
connected: sample-2
これは想定通り。
子要素を入れ子にしてから、親要素ごとまとめて一度だけdocumentにappendした場合にどうなるか。
const sampleElement1 = document.createElement("sample-elem");
sampleElement1.id = "sample-1";
const sampleElement2 = document.createElement("sample-elem");
sampleElement2.id = "sample-2";
log("before append sample-2");
sampleElement1.append(sampleElement2);
log("after append sample-2");
log("before append sample-1");
document.querySelector("body").append(sampleElement1);
log("after append sample-1");
last of body
before append sample-2
after append sample-2
before append sample-1
connected: sample-1
connected: sample-2
after append sample-1
documentにappendしたタイミングで、親要素から順にconnectedCallback
が呼び出される。createElement
した順番は関係ないし、まだconnectされていない要素にappendしてもconnectedCallback
は呼び出されない。
defer
, async
, type="module"
の関係はこのページにある図がわかりやすい。