元スレ+ JavaScript の質問用スレッド vol.122 +
JavaScript覧 / PC版 /みんなの評価 :
551 = :
>>550
有り難うございます。
わかりやすいかったです。
そういう理屈だったんですね。
552 = :
>>548
jQuery でも循環参照しているが、window unload 時に detachEvent しているからメモリリークしない
>>550は真っ赤な嘘だから信用しないように
jQueryもJavaScriptで書かれたコードに過ぎないので、script-DOM間の循環参照は起きる
553 = :
>>552
↓ こういうのが循環参照だから
window.addEventHandler('load', function() {
・・・
});
こうすればいいんだよ。↓
function onload() {・・・}
window.addEventHandler('load', onload);
クロージャーでイベントハンドラを作らないで、関数を指定すればば循環参照は起きない。
jQueryはこれに近いことをやっている。擬似コードレベルで書くと
$(window).on('load', function() {・・・});
と書いた時、jQuery内部では以下のように処理されている。
var events = {'load': []};
function on(event, handler) {
if (events['load'].length == 0) {
window.addEventHandler('load', onload);
}
events[event].push(handler);
}
function onload() {
for(var i = 0; i < events.length; i++) {
events['load'][i]();
}
}
DOMに直接登録されているイベントハンドラは、ただのjQuery内部に定義されたただの関数であり、
クロージャーは内部でイベントハンドラリスト変数に格納される。
554 = :
>>553
循環参照でメモリリークする実装用の jQuery は v1.x 系であり、v1.x系は内部で event プロパティの互換性を取る為にクロージャを形成している
この時点で循環参照が発生しているので、循環参照を回避する手段がない
555 = :
↓なぜこれが循環参照になるのかというと
window.addEventHandler('load', function() {
・・・
});
こう解釈すればわかる
function foo() {
var a = [巨大な配列];
window.addEventHandler('load', function() {
alert(a.length);
alert(foo.name);
});
}
本来、aはローカル変数だから、foo関数が終わったら開放されるはずだけど、
loadした時に呼ばれるクロージャーは、a.lengthを参照できるし、
foo.nameも見える。つまりaはずっと残り続けるし、foo関数自体がずっと残る。
これをonload関数に分けると
function onload() {
// aは見えない
}
function foo() {
var a = [巨大な配列];
window.addEventHandler('load', onload);
}
fooを実行されたらaもfooも消えるわけ。
そもそもonloadからアクセス出来ないので。
jQueryはこのように、直接イベントハンドラに登録しているものは
ただの関数となっており、間接的にイベントを処理しているからJavaScriptとDOMの循環参照にならない。
556 = :
循環参照の話、前も出てたね
テンプレに入れるとか入れないとかどうなったのかな
557 = :
>>554
> event プロパティの互換性を取る為にクロージャを形成している
eventの話も追加しないといかんのかw
window.addEventHandler('load', function(event) {・・・});
$(window).on('load', function(event) {・・・});
一見どちらもクロージャーの中でeventを参照しているように見えるが、
jQueryのeventはDOM標準のeventではない。
DOM標準のeventに形を整えた、jQuery.Eventオブジェクトになっている。
だからeventを参照しても、JavaScriptとDOMの間に直接循環参照が形成されることがない。
558 = :
window.addEventHandler('load', function(event) {・・・});
↑DOMである ↑DOMである
$(window).on('load', function(event) {・・・});
↑DOMではない ↑DOMではない
jQueryオブジェクト jQuery.Eventオブジェクト
このように、jQueryでコードを書くと
DOMを直接参照しない。
559 = :
jQueryでも、こう書けばaもfooもずっと残り続けるのでは?
function foo() {
var a = [巨大な配列];
$(window).on('load', function() {
alert(a.length);
alert(foo.name);
});
}
560 = :
>>553,552,554
理解した
便利だが、とんでもなく重たい処理だな
そこまでやるなら useCapture や handleEvent ぐらいは対応して欲しいものだが…
561 = :
>>559
そうだねw 少し説明が足りなかった。
たしかにそう。その通り。
だけど問題は「この循環参照がメモリリークを起こすかどうか」って話。
本来あるべきなのは、>>559で書いても>>555で書いても
循環参照そのものは起きるが、メモリリークは起きてはいけないってこと。
だけど、>>555では、「循環参照が起きてるのが」「JavaScriptとDOMの間であり」
「それがブラウザの問題によって」「クロージャーが無くなってもメモリリークする」
これが正しい説明。
なぜクロージャーが無くなってもメモリリークするのかというと。
JavaScriptが「クロージャーが無くなったこと」を検出できないから。
なぜならクロージャーはJavaScriptの世界にいないDOMによって管理されているから
window.addEventHandler('load', function(event) {・・・});
↑DOM ↑DOMの世界に連れて行かれたので
イベントハンドラが消えてもそのことが
JavaScriptの世界に通知されない
jQueryを使うと、クロージャ自体はまだJavaScriptの世界にいて、
そしてDOMの世界に連れて行かれるのは、jQuery内部に定義された関数だけになるので
JavaScript(のクロージャー)とDOMの間では循環参照が起きない。
562 = :
何個か前のスレでも上がってたけど、循環参照でメモリリークするのって、IE6とか昔のブラウザだけだよね?
563 = :
>>560
useCapture はIEのattachEventにはない機能なので
クロスブラウザにする上で実装不可能。
IEでaddEventListenerが使えるようになったのはIE9から。
jQueryは2006年8月リリース、IE7は2006年10月、IE9は2011年
シェアも考えるとuseCaptureに対応するのは現実的じゃない。
1系との互換性を保つ理由がなくなったらサポートされるかもね。
今は、古いブラウザ対応の1系とモダンブラウザ専用の2系は
API的には完全に同じで入れ替え可能だから。
564 = :
>>560
handleEvent って必要かな?
例えばjQueryでこのようなコードは書けるよ
var handlerObject = (function() {
var local = '';
return {
'click', function() {},
'keydown', function() {},
}
})();
$(window).on(handlerObject);
565 = :
handleEvent のサンプル見ると
handleEvent関数の中でswitchで処理を分けるもの
ばっかり出てくるけどあれださくね?
こんなの。
http://lealog.hateblo.jp/entry/2013/02/14/215952
それよりも>>564の方がswitchがない分
優れてると思う。
いまいちどういう時にhandleEventを使うと
嬉しいのかよくわからない。
566 = :
>>563
> useCapture はIEのattachEventにはない機能なので
> クロスブラウザにする上で実装不可能。
attachEvent になくとも、jQuery で独自管理しているなら出来るだろう?
attachEvent の実行順不定も独自管理で吸収しているわけだから実現できないとは思えない
567 = :
>>565
handleEvent が便利なのは this 値を制御できるところ
function Hoge () {}
Hoge.prototype.foo = function () { };
Hoge.prototype.handleEvent = function () { this.foo(); };
document.addEventListener('click', new Hoge, false);
listener 関数内で bind される this 値を変更するために余計な処理をしなくて済む
jQuery では出来ないので Function#bind 的な処理を追加する必要がある
568 = :
>>566
> attachEvent になくとも、jQuery で独自管理しているなら出来るだろう?
どうやって? これがもしでキャプチャフェーズはあるが
バブリングフェーズがない のであれば、キャプチャフェーズで
発生したイベントを保存しておいて終わったら逆に再生することで実装可能だと思う。
だけどそもそもキャプチャフェーズがないのだから、キャプチャフェーズの
発生順番なんて取得しようがないでしょ?
あぁ、(君の考えが)わかったw
ならバブリングフェーズで起きたイベントをとっておいて、
それを逆に再生してキャプチャフェーズを行えばいいって思ったね?
その後で、バブリングフェーズをエミュレート(最初のバブリングフェーズは無視する)
すればいいって考えたね。
event.preventDefault() の存在を忘れてるよ。
event.preventDefault()は本物のバブリングフェーズで実行しないと
規定の動作(リンク移動など)が実行されてしまうんだ。
569 = :
>>568
> ならバブリングフェーズで起きたイベントをとっておいて、
> それを逆に再生してキャプチャフェーズを行えばいいって思ったね?
その通り
> event.preventDefault()は本物のバブリングフェーズで実行しないと
> 規定の動作(リンク移動など)が実行されてしまうんだ。
jQuery は phase を認識できるのだから capturing phase では event.cancelable = false を設定して、event.preventDefault() を実行しなければいいのでは?
event も jQuery が管理するオブジェクトだとあなた自身が主張していたと思うのだが
570 = :
>>569
キャンセル不可能なイベントも有るんだよ。
http://developer.mozilla.org/ja/docs/Web/API/event.preventDefault
> イベントがキャンセル可能かどうかは event.cancelable を使って確認できます。
> キャンセル不可能なイベントに対して preventDefault を呼び出しても効果はありません。
各イベントにキャンセルできるかどうか書いてあったと思うけど
ちょっと面倒だから、この説明だけ書いておくけど。
>>569
> event も jQuery が管理するオブジェクトだとあなた自身が主張していたと思うのだが
jQueryが管理しているのは、イベントハンドラ。
イベント自体はDOMで発生した順番通りに処理する。
話をごっちゃにしてるぞ。
571 = :
>>567
> listener 関数内で bind される this 値を変更するために余計な処理をしなくて済む
なるほど。この違いってことだね。
//Hogeは同じ
function Hoge () {}
Hoge.prototype.foo = function () { };
Hoge.prototype.handleEvent = function () { this.foo(); };
document.addEventListener('click', new Hoge, false);
$(document).on('click', $.proxy(new Hoge, 'handleEvent'));
573 = :
>>561
>メモリリーク
load イベントのように一度しか実行されないものは別として、
ページ内に居残り続けるイベントハンドラには関係無い話
ってことでok?
574 = :
jQueryのイベントハンドラがオブジェクトの場合って
空いてると思うからhandleEventに対応するのは可能そうだね。
現状だと、こうするか>>571のようにするか、jQueryプラグインを作れば短くなるかな。
document.addEventListener('click', new Hoge, false); // オリジナル
$(document).on('click', $.proxy(new Hoge, 'handleEvent')); // 動作する
$(document).on('click', $.handle(new Hoge)); // handleプラグイン作ってみた
$(document).on('click', new Hoge); // 理想
576 = :
>>572
だからね。
1. 本物のバブリングフェーズでイベントが発生(全部キャンセルする)
2. jQueryでキャプチャフェーズをエミュレート
3. jQueryでバブリングフェーズをエミュレート
これを実現するには、本物のバブリングフェーズで
ブラウザのデフォルトの動きを、
全てキャンセルしないといけないわけ。
キャンセルできないと、jQueryでキャプチャフェーズをエミュレートする前に
ブラウザのデフォルトの処理が行われてしまう。
例えば、unloadイベントはキャンセルできない。あとcancelableはreadonlyだから注意してね。
http://developer.mozilla.org/en-US/docs/Web/Events/unload
> Bubbles No
> Cancelable No
> cancelable Read only
あとバブリングしないイベントも有るって問題もあったな。
577 = :
>>573
それが更にひどい話でページが移動しても
メモリに残り続ける場合もある。
ここまで酷いのはIE6の初期ぐらいだとは思うけど
578 = :
実際のところ、bubbling しないイベントタイプで capturing phase を実装するのは難しいだろうが、bubbling するイベントなら独自管理できると思う
1. イベント伝播先の最終地点(document or window)に一つだけ addEventListener し、bubbling 過程でユーザ定義されたイベントを全て拾う
2. capturing phase を降順再生
3. bubbling phase を昇順再生
bubbling しないイベントタイプでは全要素にイベント定義して capturing phase を降順再生する必要があるので、全く現実的ではない
579 = :
>>578
限定的なら可能かもしれないけど、そうすると
このイベントはブラウザから出て、このイベントはjQueryがエミュレートするとかで
実行順とか細かい動きの違いが出て、ハマる元になると思う。
DOMでさえ、イベント事にキャンセルできるか出来ないか
バブリングするかしないかって違いがあるのに、
さらにjQueryが絡んでくるとかさ。
jQueryはDOMを単純にすることが使命であって
DOMよりも複雑にしてしまったら本末転倒。
580 = :
まあ、こんな所でごちゃごちゃ言ってるが、
キャプチャフェーズへの対応は検討されたけど
無理という理由でとっくの昔に却下された話だろうな。
こんなDOMの基本的な機能をjQuery開発者が見逃すわけ無いし、
追加で対応できるようなものならとっくにしてるだろうさ。
581 = :
>>576
いいたいことはわかったし、>>578の問題点もはっきりした
であるならば、こんな方法はどうだろう?
1. document.attachEvent('onclick', listener) を一つだけ定義
2. bubbling phase で on() でユーザ定義されたイベントを検知
3. この時、"capturing event" として定義されたイベントを全て実行してしまう
4. "bubbling event" を通常通り実行
初回、イベント検知時に全ての capturing phase を実行すれば良い
これなら bubbling phase でイベントをキャンセル必要がなくなる
> unloadイベントはキャンセルできない
unload イベントは Bubbles No だから初めからイベントの実行順は自明のはず
同じDOMを参照する2つの unload があるなら予め実行順を定義する事が出来る
ただ、event.target !== event.currentTarget の状況を想定するなら確かにエミュレートできない
Bubbles No なイベントで capturing phase を実現する完全な方法は存在しないと思う
582 = :
>>581の補足。
> unload イベントは Bubbles No だから初めからイベントの実行順は自明のはず
> 同じDOMを参照する2つの unload があるなら予め実行順を定義する事が出来る
"capturing event" -> "bubbling event" の実行順になる、ということ
583 = :
useCapture の件とは違って handleEventに関しては
対応できないことは、思えなかったので探してきた。
結論を言うと、handleEventは3.0で対応しそうだよ。
http://bugs.jquery.com/ticket/12031
→http://github.com/jquery/jquery/issues/1735
まだOpen状態だけど、Milestone 3.0.0 になっている
584 = :
>>581
>>579で書いたように限定的になら対応可能だと思うけど
限定的でしか無いし、場合によっては複雑になる場合もある。
JavaScriptで書いたコードすべてがjQueryを使っているのなら
ましかもしれないけど、生DOMで書いてるコードがあるかも知れないし
その他のライブラリを使ってるかもしれない。
jQueryはフレームワークじゃなくて、ライブラリなのだから
混ぜて使うことも考えるべき。(だからnoConflictというのもある)
気づいていない人も多いと思うけど、jQueryのイベントハンドラって
DOMのイベントハンドラをそのまま使うことも出来るんだよ。
その為にthisはDOM要素だし、eventはDOMのeventと互換性を持たせてある。
つまりjQueryのイベントハンドラの入り口はDOMと互換性がある。
jQueryはそこまで気を使って作られてるんだよ。
できそうかどうかの話じゃなくて、それを実現した結果嬉しいかどうか。
jQueryを使うことで混乱が生じるのであれば、最悪jQueryは
使わないほうがいいってことになってしまう。
俺はjQuery開発者じゃないから、本当はどう考えているかわからないが、
俺なら、以上の理由でキャプチャフェーズへの対応は却下するよ。
(1.xがウェブから消えて互換性を切り捨てていい時代になれば話は別)
585 = :
>>584
> 限定的でしか無いし、場合によっては複雑になる場合もある。
確かに自分だったら対応しないだろうな
実験的には面白そうな試みではあるが、公開しないという判断は理解できる
> その為にthisはDOM要素だし、eventはDOMのeventと互換性を持たせてある。
this は元々は独自拡張で DOM 4 が後追いで標準化されたけどな
また、jQueryのイベントは DOM 標準プロパティも使えるが、かなり拡張してある
event.delegateTarget は event.currentTarget と同値だし、
event.data というコンテナは handleEvent があれば不要になったはずのプロパティだし、
event.isDefaultPrevented(), event.result とかいうコーディングの書き方次第で不要なプロパティはあるし...etc
jQuery の event からはDOM標準も使えるけど、どんどん拡張しようとするスタンスが見え隠れしてる
今は修正されているが、古くは .attr() の仕様がプロパティ参照/属性参照の曖昧さ加減が酷いものだった
実際、jQueryコードの内容を見ても標準を使うというよりは独自路線を突き進んでいる感が強い気がする
Polyfill 的なコードより独自拡張路線のスタンスが強いように感じる
586 = :
まとめると、
未来のこと。これは想像でしか無い。
過去のこと。これは昔は悪かったが今は改善された。
そういうことだろう?
588 = :
useCapture、勉強したとき以来使ったことない…
普通のサイトで使うことなんてあるのかね
589 = :
>>587
だからこそ「互換性を持たせて混乱しないように」という主張に疑問を感じる
590 = :
>>588
useCapture を使うと Bubbles No なイベントでも capturing phase ではイベントが伝播するのが便利
591 = :
>>589
> だからこそ「互換性を持たせて混乱しないように」という主張に疑問を感じる
どこに疑問を感じるの?
「俺の考えた未来ではjQueryは互換性を持たせないようになっているはずだ」
という、な~んの根拠もない主張はいらないよ?
592 = :
jQueryが使いづらい/分かりづらいと思う点
- jQuery.proxy() の名前が分かりづらい(Function#bind と名前が全く違う)
- jQuery.data() でdata-独自属性の参照規則が複雑
- var obj = {}; obj.window = obj;console.log(jQuery.isWindow(obj)); // true
- isXMLDoc() も同様。[[Class]] 判定ぐらいせよと
- テキストノード操作APIがない(DOM API を使わないとやってられない)
API名からして大分違うし、ECMAScript, DOM に慣れ親しんだ人がすぐ使えるライブラリではないよなー
「混乱上等、jQuery の世界にどっぷりつかって来てね」というメッセージを感じる
対象ブラウザの下限を上げれば、ES5, Selectors API で十分って人もいるだろう
593 = :
jQueryは古くからdata()メソッドというものを持っていた。
これはDOMに関連付けられたデータを保持するメソッドであるが、
HTML5になってから、HTMLにdata-*属性という仕様が追加された。
jQueryはその標準仕様に対応し、dataメソッドでdata-*属性を
取得できるようにした。
594 = :
>>591
ある程度、Interface の名前や機能に互換性を持たせて jQuery 習得コストを下げるという意味ではないのか?
jQueryの場合は、既に普及している事もあって今までに作り上げた jQuery の世界観を崩さないようにすることを重視しているように見える
DOM Interface との互換性を重視しているようには見えず、新参者が混乱してもおかしくないと思う
595 = :
テキストノード関係はほんと強化して欲しい
596 = :
>>593
歴史的にはわかるけど、data() で data 独自属性を参照可能にする必要はなかったような
attr() で参照可能なんだから、data() を拡張する必然性がない
一つのメソッドで複数の事が出来るのは便利だけど、わかりやすさからかけ離れてしまう面もあるよね
597 = :
実際、ES5 で生産性は上がっているから jQuery を使う必然性は下がってきてはいるんだよな
Polyfill を使えば、ある程度は古いブラウザにも対応できる
Selectors API は複雑なので IE7 以下をサポートすると大変だが
598 = :
>>592
> - jQuery.proxy() の名前が分かりづらい(Function#bind と名前が全く違う)
それはproxyはbindではないから。使い方が違う。
proxyは他のメソッドに処理を転送するために有る
他のメソッドに処理を転送するのが目的なので、メソッド名を
文字列で指定できたり、引数の追加などの機能がある
> - jQuery.data() でdata-独自属性の参照規則が複雑
難しくないので何を言っているのかさっぱりわからない。
念の為に言っておくと、"属性"を読み書きするのはattrであり
data()は属性ではなくHTMLに関連付けられたデータを取得するだけ(data-*はデータの初期値でしか無い)
> - var obj = {}; obj.window = obj;console.log(jQuery.isWindow(obj)); // true
完全に見分ける方法はないので、こんなこと普通やらんだろw というモアベターな方法でチェックしてる。
> - isXMLDoc() も同様。[[Class]] 判定ぐらいせよと
やる価値がない。そんなエッジケースに対応して遅くなる方が問題
> - テキストノード操作APIがない(DOM API を使わないとやってられない)
$('#id').contents().filter(function() {
return this.nodeType === 3;
}).each(function() {
console.log(this);
});
599 = :
>>596
> 歴史的にはわかるけど、data() で data 独自属性を参照可能にする必要はなかったような
> attr() で参照可能なんだから、data() を拡張する必然性がない
attrでは面倒くさい。
<p id="id" data-a="a" data-b="b"></p>
console.log($('#id').data());
// → Object { a="a", b="b"}
600 = :
HTML5 に追従するなら element.dataset で実装して欲しかった
あんな中途半端な実装なら getAttribute() 互換の attr() で十分だよ
http://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes:dom-dataset
類似してるかもしれないスレッド
- + JavaScript の質問用スレッド vol.122 + (116) - [100%] - 2018/5/2 18:30
- + JavaScript の質問用スレッド vol.125 + (1001) - [97%] - 2015/10/7 17:45
- + JavaScript の質問用スレッド vol.123 + (966) - [97%] - 2020/10/20 2:30
- + JavaScript の質問用スレッド vol.120 + (1002) - [97%] - 2014/11/8 1:15
- + JavaScript の質問用スレッド vol.124 + (1001) - [97%] - 2015/7/16 1:30
- + JavaScript の質問用スレッド vol.121 + (1001) - [97%] - 2022/11/29 16:30
- + JavaScript の質問用スレッド vol.132 + (1001) - [97%] - 2018/4/19 11:00
- + JavaScript の質問用スレッド vol.142 + (984) - [97%] - 2020/8/27 19:15
- + JavaScript の質問用スレッド vol.112 + (1001) - [97%] - 2013/11/27 16:46
- + JavaScript の質問用スレッド vol.121 + (1001) - [97%] - 2015/1/1 18:30
- + JavaScript の質問用スレッド vol.129 + (981) - [97%] - 2016/5/5 8:16
- + JavaScript の質問用スレッド vol.129 + (926) - [97%] - 2017/7/27 13:45
- + JavaScript の質問用スレッド vol.128 + (1001) - [97%] - 2016/2/26 6:45
- + JavaScript の質問用スレッド vol.123 + (1002) - [97%] - 2015/4/27 23:30
- + JavaScript の質問用スレッド vol.127 + (1001) - [97%] - 2016/2/4 0:15
- + JavaScript の質問用スレッド vol.127 + (160) - [97%] - 2021/7/16 9:30
- + JavaScript の質問用スレッド vol.142 + (926) - [97%] - 2019/12/23 13:15
トップメニューへ / →のくす牧場書庫について