グローバルに on*** 関数を関数宣言構文で定義してもリスナにならない
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ window.on*** プロパティへの代入やグローバル変数定義では元から用意されてる setter が使われてリスナ定義される
◆ グローバルで onclick 関数を宣言して window.on*** プロパティに代入すると property descriptor が更新されて setter が使われず value 形式で設定される
◆ 結果として一度関数宣言で on*** プロパティを作るとそれ以降リスナを更新できなくなる
◆ グローバルで onclick 関数を宣言して window.on*** プロパティに代入すると property descriptor が更新されて setter が使われず value 形式で設定される
◆ 結果として一度関数宣言で on*** プロパティを作るとそれ以降リスナを更新できなくなる
全体に対してリスナを付けるときは window に対して addEventListener メソッドを使います
シンプルなページだと window.onclick のようなプロパティに直接関数を代入することもあるかと思います
JavaScript では window がグローバルオブジェクトなので window.onclick への代入というのはグローバル変数 onclick に代入しているのといっしょです
また グローバルのスコープで関数宣言をするとグローバルに関数名の変数ができます
なので
イベントを起こすと実行されてメッセージが表示されています
このあとに関数構文でグローバルに onclick を定義して上書きします
上書きはされているのにリスナは変更されていないようで 表示される文字は古いままです
イベントを起こしても何も起きません
この後で window のプロパティに代入形式で onclick 設定します
これでも何も起きません
また 最初にどっちで設定するかも大切で 先に関数宣言で window.onclick を作ってしまうとその後にプロパティ宣言してもダメのようです
何が違うのかが気になるので property descriptor を見てみます
プロパティ代入
関数宣言
設定が違ってますね
on*** は特殊そうなので その他通常のプロパティで試してみます
configurable の true/false はこちらも違っていますが 値を getter/setter で取得設定するか 単純に value で入っているかは on*** だけの特徴みたいです
configurable から想像できますが後から上書きしたときどうなるかもみてみます
先にプロパティへの代入であとから関数宣言の場合は configurable が最初は true なので変更されます
しかし 最初から関数宣言した時と同じように value が設定されてリスナとしては機能しません
先に関数宣言をした場合は configurable が false なので変更されず getter/setter 形式にはなりません
そのときの property descriptor は
プロパティへの代入だと setter による処理が行われます
これによってリスナ登録が行われてるようです
普通のグローバル変数の作成でも setter が定義されていればそれが使われます
デフォルトでは configurable が true なので property descriptor は変更可能です
関数宣言をすると value 形式に置き換えてプロパティが再定義され setter が使われないのでリスナは設定されません
この後にプロパティに代入しても setter はすでになくなっていて value が置き換えられるだけになり リスナは設定されません
Object.defineProperty で設定しなおそうにも configurable が false に設定されてしまうので再定義しようとするとエラーになります
まとめると window のプロパティに存在する on*** って名前の関数宣言はやめておいたほうがいいでしょう
Firefox でも同じ動作でした
そういえばこの記事でなんで onclick の中で onclick 呼び出そうとしたのか不明でしたがこういうことしたかったのかも
シンプルなページだと window.onclick のようなプロパティに直接関数を代入することもあるかと思います
JavaScript では window がグローバルオブジェクトなので window.onclick への代入というのはグローバル変数 onclick に代入しているのといっしょです
また グローバルのスコープで関数宣言をするとグローバルに関数名の変数ができます
なので
window.onclick = function(){}
とfunction onclick(){}
はどちらも window.onclick で関数を取得できて同じことのように見えますwindow のプロパティに代入形式で onclick 設定
普通にプロパティ代入でリスナを設定するとこのように動きますwindow.onclick = function(eve){console.log("defined by assignment of function expression")}
console.log(window.onclick)
// ƒ (eve){console.log("defined by assignment of function expression")}
window.dispatchEvent(new Event("click"))
// defined by assignment of function expression
console.log(window.onclick)
// ƒ (eve){console.log("defined by assignment of function expression")}
window.dispatchEvent(new Event("click"))
// defined by assignment of function expression
イベントを起こすと実行されてメッセージが表示されています
このあとに関数構文でグローバルに onclick を定義して上書きします
function onclick(){console.log("defined by function statement")}
console.log(window.onclick)
// ƒ onclick(){console.log("defined by function statement")}
window.dispatchEvent(new Event("click"))
// defined by assignment of function expression
console.log(window.onclick)
// ƒ onclick(){console.log("defined by function statement")}
window.dispatchEvent(new Event("click"))
// defined by assignment of function expression
上書きはされているのにリスナは変更されていないようで 表示される文字は古いままです
先に関数構文でグローバルに onclick 定義
次は先に関数宣言した場合ですfunction onclick(){console.log("defined by function statement")}
console.log(window.onclick)
// ƒ onclick(){console.log("defined by function statement")}
window.dispatchEvent(new Event("click"))
console.log(window.onclick)
// ƒ onclick(){console.log("defined by function statement")}
window.dispatchEvent(new Event("click"))
イベントを起こしても何も起きません
この後で window のプロパティに代入形式で onclick 設定します
window.onclick = function(eve){console.log("defined by assignment of function expression")}
console.log(window.onclick)
// ƒ (eve){console.log("defined by assignment of function expression")}
window.dispatchEvent(new Event("click"))
console.log(window.onclick)
// ƒ (eve){console.log("defined by assignment of function expression")}
window.dispatchEvent(new Event("click"))
これでも何も起きません
プロパティ代入しないとダメ
関数宣言で window.onclick を作ってもリスナとして実行はされないようですまた 最初にどっちで設定するかも大切で 先に関数宣言で window.onclick を作ってしまうとその後にプロパティ宣言してもダメのようです
何が違うのかが気になるので property descriptor を見てみます
プロパティ代入
window.onclick = function(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(window, "onclick")
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}
関数宣言
function onclick(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
設定が違ってますね
on*** は特殊そうなので その他通常のプロパティで試してみます
window.foo = function(){}
Object.getOwnPropertyDescriptor(window, "foo")
// {value: ƒ, writable: true, enumerable: true, configurable: true}
function bar(){}
Object.getOwnPropertyDescriptor(window, "bar")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(window, "foo")
// {value: ƒ, writable: true, enumerable: true, configurable: true}
function bar(){}
Object.getOwnPropertyDescriptor(window, "bar")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
configurable の true/false はこちらも違っていますが 値を getter/setter で取得設定するか 単純に value で入っているかは on*** だけの特徴みたいです
configurable から想像できますが後から上書きしたときどうなるかもみてみます
window.onclick = function(){}
function onclick(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
function onclick(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
先にプロパティへの代入であとから関数宣言の場合は configurable が最初は true なので変更されます
しかし 最初から関数宣言した時と同じように value が設定されてリスナとしては機能しません
function onclick(){}
window.onclick = function(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
window.onclick = function(){}
Object.getOwnPropertyDescriptor(window, "onclick")
// {value: ƒ, writable: true, enumerable: true, configurable: false}
先に関数宣言をした場合は configurable が false なので変更されず getter/setter 形式にはなりません
setter で設定されるとリスナにも登録される
何もしないページ開いた直後の状態でも onclick プロパティは null として存在しますそのときの property descriptor は
{get: ƒ, set: ƒ, enumerable: true, configurable: true}
プロパティへの代入だと setter による処理が行われます
これによってリスナ登録が行われてるようです
普通のグローバル変数の作成でも setter が定義されていればそれが使われます
Object.defineProperty(window, "foo", {
get(){return 1},
set(value){console.log("setter: ", value)},
})
var foo = 100
// setter: 100
console.log(foo)
// 1
get(){return 1},
set(value){console.log("setter: ", value)},
})
var foo = 100
// setter: 100
console.log(foo)
// 1
↑はコピペだと var の宣言が先に行われて再定義エラーになります
foo が元から定義済みであるようにみせるため defineProperty を先に実行しておいて あとから foo 定義をするとコメントのような結果になります
(コンソールで 2 回に分けて実行するのがかんたん)
foo が元から定義済みであるようにみせるため defineProperty を先に実行しておいて あとから foo 定義をするとコメントのような結果になります
(コンソールで 2 回に分けて実行するのがかんたん)
デフォルトでは configurable が true なので property descriptor は変更可能です
関数宣言をすると value 形式に置き換えてプロパティが再定義され setter が使われないのでリスナは設定されません
この後にプロパティに代入しても setter はすでになくなっていて value が置き換えられるだけになり リスナは設定されません
Object.defineProperty で設定しなおそうにも configurable が false に設定されてしまうので再定義しようとするとエラーになります
まとめると window のプロパティに存在する on*** って名前の関数宣言はやめておいたほうがいいでしょう
Firefox でも同じ動作でした
そういえばこの記事でなんで onclick の中で onclick 呼び出そうとしたのか不明でしたがこういうことしたかったのかも
<head>
<script>
function onclick(){
}
</script>
</head>
<body onclick="onclick()">
</body>
<script>
function onclick(){
}
</script>
</head>
<body onclick="onclick()">
</body>