以前のエントリー でprototype.js1.5のElement.Methodsについて書きました。 前回はいいところをプッシュして書いたんですが、皆さんご存じの通り問題児でもあります。
Element.Methodsの問題点
- elementを拡張するかしないかが選択できない。
- プロトタイプチェーンがつながっていない。
- document.createElementが考慮されていない。
■elementを拡張するかしないかが選択できない。
まずわかりやすくこれから。elementは取得された時に拡張されるわけですが、当然パフォーマンスに影響があります。使いたくない人もいるでしょう。しかし、Element.extendによる拡張は$にも$$にも組み込まれていて選択できません。メソッドを置き換えて回避は可能ですが、組み込まれている、ということを考慮すると当然extendされている、ということを前提としたライブラリも出てくるでしょう。となった場合にやっかいです。別名のメソッドを定義してもいいですが・・・やっぱり$って名前がいいよね!(何
■プロトタイプチェーンがつながっていない。
これは当然の問題。Element.extendは文字通りelementにElement.Methodsのメソッドをコピーしているだけ。しかも一回取得すると、_extendedというフラグが立って次回からはもうextendされない(パフォーマンスを考慮してのこと)。つまり Element.Methodsへのメソッド追加が反映されない、メソッドの内容変更も反映されない。 つまり、恐らく$か$$されるだろう、window.onloadイベント前にElement.Methodsへのメソッド追加を完了させ、それ以降変化させない、という事に注意しないといけない。
ではなぜプロトタイプチェーンがつながっていないのだろうか。Elementは拡張できないんだろうか。これはYesでありNoです。 まず、このElement.Methodsによる害をある程度押さえ込むことのできるソースを示します。 これはFirefoxなどMozilla系、safari、Operaで有効です。
(function() {
if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
if(window.HTMLElement) {
var methods = Element.Methods, property;
var bind = function(name, method) {
HTMLElement.prototype[name] = function(){
Array.prototype.unshift.call(arguments, this);
return method.apply(null, arguments) || this;
}
}
for (property in methods) {
var value = methods[property];
if (typeof value == 'function') bind(property, value);
}
HTMLElement.prototype._extended = true;
}
})();
このコードを 最後に(window.onloadの直前で)実行すれば OKです(scriptaculousとかもElement.Methodsを拡張するので、その拡張が終わった後)。 これでelementがプロトタイプチェーンでつながっているブラウザでは$による負荷を軽減できます。$(“test”).show()とかも実行できますし、引数操作によるパフォーマンス低下がいやならElement.show(“test”)と実行すれば良いだけです。しかも地味にreturn method.apply(null, arguments) || this;とすることによって$(“test”).update(“hogehoge”).show()とか書けるようにもしてみました(笑
しかしIEはどうしようもありません。 そもそもIEはelementがプロトタイプチェーンをもっていません。 document.createElement (“div”).constructorとやってもprototypeとやってもundefinedと抜かす強敵です。これは昔から対策が考えられていますが、つまるところElement.Methodsと同様にコピーするだけです。それをhtcをつかってみたり、コンストラクタを擬似的に作ってみたり、とかっていう方法で実装しているだけです。なのでElement prototypingしようと思うとIEでは現状これが限界です。
■ document.createElementが考慮されていない。
先ほど示したソースでelementがプロトタイプチェーンをもつブラウザでは当然document.createElementで作成した elementもElement.Methodsのメソッドが実行できます。しかしIEではそれができません。この document.createElementも考慮して先ほどのElement.Methods対策ソースを改良しましょう。
(function() {
if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
if(window.HTMLElement) {
var methods = Element.Methods, property;
var bind = function(name, method) {
HTMLElement.prototype[name] = function(){
Array.prototype.unshift.call(arguments, this);
return method.apply(null, arguments) || this;
}
}
for (property in methods) {
var value = methods[property];
if (typeof value == 'function') bind(property, value);
}
HTMLElement.prototype._extended = true;
}else {
document._createElement = document.createElement;
document.createElement = function(tag){
return Element.extend(document._createElement(tag));
}
}
})();
赤色で示した部分が追加部分です。これでIEでもdocument.createElementした後に直接Element.Methodsのメソッドが実行できます。何度も言いますがパフォーマンス低下がいやな人はこの追加部分を省いてください。
というわけで今回はElement.Methodsの問題への対応策を示してみました。要はIEがelementがプロトタイプチェーンを持つようにしてくれれば万事解決なわけなんですよね。でも結局IE7でも実装されていないようで、なかなか・・・