はじめに
よく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
で確認。