はじめに
よくJavascriptはbodyの閉じタグの前に書くか、それより前で書くなら別ファイルにしてdeferをつけろと言われる。最近この言葉を守らないではまった事例があるのでご紹介。
事例
以下のようにWebComponentsを定義する。
customElements.define(
'constructor-attribute'
, class extends HTMLElement {
constructor() {
super()
this.textContent = this.getAttribute( 'replace' )
}
}
)
replaceという名前の属性の値をtextContentにセットするだけのwebComponentsである。
<constructor-attribute replace =REPLACED>INITIAL</constructor-attribute>
<script src="main.js"></script>
とやると、期待通りブラウザにREPLACEDと表示される。
<script src=main.js defer></script>
<constructor-attribute replace=REPLACED>INITIAL</constructor-attribute>
でも期待通りブラウザにREPLACEDと表示される。
ところがscriptのdeferをはずして、
<script src=main.js></script>
<constructor-attribute replace=REPLACED>INITIAL</constructor-attribute>
とやると、constructorの中のgetAttributeでnullが帰ってくるので、ブラウザにはINITIALと表示される。
対処
bodyの閉じタグの前に書いてやるか、それより前に書くのなら別ファイルにしてdeferをつける
でもどうしても head で書きたいときとかは、別解?を参照。
別解?
どこにでもおけるようなものを作りたいと思い、次のようにしてみたらちょいハマりした。
customElements.define(
'constructor-attribute'
, class extends HTMLElement {
static get
observedAttributes() {
return [ 'replace' ]
}
attributeChangedCallback() {
this.textContent = this.getAttribute( 'replace' )
}
}
)
observedAttributesでウォッチしたい属性を登録しておけば、constructor直後にattributeChangedCallbackが呼ばれるのでここで処理しておけばいい気がしたのでやってみた。
<constructor-attribute replace=REPLACED>INITIAL</constructor-attribute>
<script src=main.js></script>
<script src=main.js defer></script>
<constructor-attribute replace=REPLACED>INITIAL</constructor-attribute>
上の二つは期待どおりREPLACEDと表示される。
<script src=main.js></script>
<constructor-attribute replace=REPLACED>INITIAL</constructor-attribute>
これもうまくいくと思っていたのだが、実際はREPLACEDINITIALと表示される
constructorとattributeChangedCallbackは同一のランループで処理されて、そのあと、タグ中の文字列がアペンドされるようだ。chrome,safari,firefox,operaで確認。