以前のエントリー で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で有効です。
1(function() {
2if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
3if(window.HTMLElement) {
4 var methods = Element.Methods, property;
5 var bind = function(name, method) {
6 HTMLElement.prototype[name] = function(){
7 Array.prototype.unshift.call(arguments, this);
8 return method.apply(null, arguments) || this;
9 }
10 }
11 for (property in methods) {
12 var value = methods[property];
13 if (typeof value == 'function') bind(property, value);
14 }
15 HTMLElement.prototype._extended = true;
16}
17
18})();
このコードを 最後に(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対策ソースを改良しましょう。
1(function() {
2if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
3if(window.HTMLElement) {
4 var methods = Element.Methods, property;
5 var bind = function(name, method) {
6 HTMLElement.prototype[name] = function(){
7 Array.prototype.unshift.call(arguments, this);
8 return method.apply(null, arguments) || this;
9 }
10 }
11 for (property in methods) {
12 var value = methods[property];
13 if (typeof value == 'function') bind(property, value);
14 }
15 HTMLElement.prototype._extended = true;
16}else {
17 document._createElement = document.createElement;
18 document.createElement = function(tag){
19 return Element.extend(document._createElement(tag));
20 }
21}
22
23})();
赤色で示した部分が追加部分です。これでIEでもdocument.createElementした後に直接Element.Methodsのメソッドが実行できます。何度も言いますがパフォーマンス低下がいやな人はこの追加部分を省いてください。
というわけで今回はElement.Methodsの問題への対応策を示してみました。要はIEがelementがプロトタイプチェーンを持つようにしてくれれば万事解決なわけなんですよね。でも結局IE7でも実装されていないようで、なかなか・・・