window.setInterval は無限ループしたり実行順を保証できない場合がある
window.setInterval の問題点
window.setInterval には window.setTimeout にない2つの問題点があります。
- 致命的なエラーが発生してもタスクを繰り返し実行する。
- 2秒かかるタスクを1秒おきに予約すると、現在のタスクが終わる前に次のタスクが始まる。
エラーが発生してもタスクを繰り返し実行する問題
/** * (サンプル1) window.setTimeout */ function timeoutTask () { var i = 0; var timeId; function handle () { throw new Error(i); // Error: 0 if (i++ < 10) { clearTimeout(timeId); timeId = setTimeout(handle, 1000); } } handle(); } timeoutTask(); /** * (サンプル2) window.setInterval */ function intervalTask () { var i = 0; var timeId; function handle () { throw new Error(i); // Error: 0 if (++i > 9) { clearInterval(timeId); } } timeId = setInterval(handle, 1000); } intervalTask();
window.setInterval ではエラーを無限に繰り返し続けます。++i を実行する前にエラーが発生しているところに問題があります。
(サンプル2) は1秒後からタスクを実行していますが、(サンプル1) と同じように即時実行型にすることでこの問題を回避できます。
handle(); timeId = setInterval(handle, 1000);
handle(); で Error になるので、setInterval が実行される前にスクリプトを終了することが出来ます。
現在のタスクが終わる前に次のタスクが始まる可能性がある問題
例えば、2秒かかるタスクを1秒おきに予約すると、現在のタスクが終わる前に次のタスクが始まります。場合によっては次のタスクが終わる時刻も現在のタスクより早いかもしれません。
var i = 0; var timeId; var busy; // ビジー状態を表す変数 function handle () { if (busy) { // ビジー状態なら1秒後に試行する clearInterval(timeId); timeId = setInterval(handle, 1000); return; } busy = true; // ビジー状態のフラグを立てる // 2秒以上かかる処理 if (++i > 9) { clearInterval(timeId); } } timeId = setInterval(handle, 1000);
このようにビジー状態であることを認識するようにコードを書けば回避できますが、これほど複雑なコードを書くぐらいなら window.setTimeout を採用する方が現実的だと思います。
結論
window.setInterval は実行順を保証できず、前後の処理が衝突する場合があり、無限にエラーが増殖する場合があります。
window.setTimeout と比較すると欠点ばかりでいいところが見つからなかったのが残念です…。
Google で Proxomitron が機能しないのを回避するフィルタ
フィルタ
[HTTP headers] In = FALSE Out = TRUE Key = "Accept-Encoding: kill sdch (out) [2011/05/14]" Match = "(\#,|)sdch(,\#|)" Replace = "\@"
原因
Google Chrome は「Shared Dictionary Compression over HTTP (SDCH)」という独自の圧縮プロトコルが組み込まれており、Proxomitron はこれを解釈できません。
これはHTTPレスポンスヘッダ「Content-Encoding」を見ることで確認できます。
Content-Encoding: sdch,gzip
ActiveXObjectとスクリプトエンジン間の循環参照によるメモリリーク
IE6 SP2- の ActiveXObject 周りのメモリリーク問題はあまり知られていないような気がしたので、簡単にまとめておきます。
jQuery や prototype.js では以下の方法で回避しています。
/** * メモリリークを回避する方法 (jQuery および prototype.js 方式) */ function empty () { // この関数は何も実行せず、何も参照しない位置に置く。何も参照しない故に循環参照しなくなる。 } function get (url) { var xhr = new ActiveXObject('Msxml2.XMLHTTP.6.0'); xhr.onreadystatechange = function () { // この関数は xhr を参照可能→循環参照している if (xhr.readyState === 4) { xhr.onreadystatechange = empty; // xhr を参照不可能な empty を代入したので循環参照しなくなる } }; xhr.open('GET', url, false); xhr.send(null); } get('/');
上記をもう少し簡単にしたコードがこちら。
/** * メモリリークを回避する方法 (new Function 方式) */ function get (url) { var xhr = new ActiveXObject('Msxml2.XMLHTTP.6.0'); xhr.onreadystatechange = function () { // この関数は xhr を参照可能→循環参照している if (xhr.readyState === 4) { xhr.onreadystatechange = new Function; // Function コンストラクタによって生成されたオブジェクトは xhr を参照不可能なので循環参照しなくなる } }; xhr.open('GET', url, false); xhr.send(null); } get('/');
jQuery 方式では function empty の位置が重要で書き方を間違えると循環参照してしまいます。
new Function 方式では空の関数をその場で生成するので、置き場所に悩まなくて済みます。
XPath の transelate() を JavaScript で実装する
概要
XPath の translate() を JavaScript で実装しました。
XPath の仕様通りに実装した translate.js と機能拡張した translate-by-array.js があります。
パフォーマンス比較
jsPerf で比較テストした範囲では最速になりました。
高速化Tips
- ECMAScript の原理上、グローバル変数が遅い(スコープチェーン)のでローカル変数にキャッシュしてます。
- ECMAScript の原理上、プロパティアクセス演算子が遅い(プロトタイプチェーン)ので2回以上同じプロパティを参照する場合はローカル変数にキャッシュしてます。オブジェクトで辞書を作る方法も控えました。
- 同じプロパティ参照でも new Object と new Array では new Array の方が若干速い気がします。ECMAScript 規定上は変化ないはずですが、Firefox は配列だけ特別な実装をしていたとどこかの記事で読んだ覚えが…。(array[i] と object.property で実装が異なる)
- String#indexOf も遅くないようです。id:babu_babu_baboo さんは string.split('') で配列化して Array#indexOf していましたが、string版なら String#indexOf だけの方がコストが軽くなるんじゃないかな、と。
- Array#push は IE7- だと遅いそうですが、最近の実装は逆なのであえて Array#push を使用しています。動作保証に IE7- を含めるなら条件付きコンパイルを使うと良いかも。
- 置換文字列の ToString() は Array#join で処理されるので String() を省略しました。
- 検索文字列長が置換文字列長より大きい場合に削除する方法に ToString(undefined) === '' を利用しています。Array#join 時に空文字に変換されます。
参考URL
id:babu_babu_baboo さんのブログに寄せされた情報を参考にさせていただきました。gtlt さんにはいつもお世話になってます。m(_ _)m
Twitterログ
translate.js : XPath の translate() 関数。 — Gist
https://gist.github.com/966219 #ECMAScript
2011-05-11 - babu_babu_babooのごみ箱
http://goo.gl/OFRFz
http://jsperf.com/translate-function-of-xpath #JavaScript #XPath
たった今、"char" が #ES3 の「将来の予約語」であることを確認したところです、はいw
http://goo.gl/0HUIQ
<cite> http://goo.gl/0Ll42 </cite> #XPath
translate-by-array.js を追加した。
https://gist.github.com/966219 #JavaScript #XPath
全角/半角文字を変換する
http://goo.gl/6farV (pdf), http://goo.gl/pOdBJ (chats-html) #Unicode
http://goo.gl/qadbY (pdf), http://goo.gl/QXYvA (chats-html) #Unicode
babu_babu_baboo さん作 UUID生成器 (version 5) を試してみる
addEventListener に { handleEvent: ... } のリスナーを渡せるかは DOM Level 3 Events の規定外・実装依存です
http://goo.gl/zAOue /
な、なんだってー!?(AA略) #DOM #JavaScript
カスタムイベントを発火させれば { handleEvent: ... } に対応しているか、を検出できるかな。