2007年04月12日 23:30 [Edit]
javascript - 勝手に添削 - JavaScript入門
釈迦に説法を承知で。
Object.prototype = { /* ... */ }は避けるべし
みにくいのでオブジェクトを入れ替える方式に
Box.prototype = {
speed: 4,
move: function() {
this.left += this.speed;
this.element.style.left = this.left + 'px';
if (this.left > 400) clearInterval(this.id);
},
start: function() {
var self = this;
this.id = setInterval(function() { self.move() }, 20);
}
};
これだと、Boxが他のプロトタイプを継承していた場合に、親オブジェクトのプロトタイプにアクセスできなくなってしまう。極端な例だと
function MyBox(id){
this.element = document.getElementById(id);
this.left = 0;
}
MyBox.prototype = Box.prototype; // ここで継承しているのに
MyBox.prototype = { // ここで台無し
speed:8
};
このあたりは、プロトタイプベースの欠点で、クラスベースのものだとclass Klass{...}が複数回登場しても、前のものを「上書き」してしまうのではなく、「重ね合わせ」になるのだけど、JavaScriptの場合prototypeもまた単なるオブジェクトなので、丸ごと代入するとそこで定義されていないものは丸ごと消えてしまう。
ここは面倒でも
Box.prototype.speed = 4;
Box.prototype.move = function() {
this.left += this.speed;
this.element.style.left = this.left + 'px';
if (this.left > 400) clearInterval(this.id);
};
Box.prototype.start = function() {
var self = this;
this.id = setInterval(function() { self.move() }, 20);
};
で留めておくか、
(function(c, o){
for (var p in o){
c.prototype[p] = o[p];
}
})(Box, {
speed: 4,
move: function() { /* ... */ },
start: function() { /* ... */ }
});
とするか、
Object.prototype.addPrototypes = function(o){ // 便利だけど推奨できない
for (var p in o){
this.prototype[p] = o[p];
}
};
Box.addPrototypes({
speed: 4,
move: function() { /* ... */ },
start: function() { /* ... */ }
})
var ary = [];
alert(ary.speed);
とするか、prototype.jsのObject.extend()を使うかするか、とにかく「重ね合わせ」をする癖をつけておいた方がいいと思う。Proto.prototype = { /* ... */ }は私も使いたい誘惑によくかられるのだけど、今後JSはさらに大規模の開発にも使われて行くはずなので。
with()は徹底的に避けるべし
次。
複数要素
for (var i = 0, l = nl.length; i < l; i ++) {
var e = nl[i];
with({e:e})
setTimeout(function() {
var box = new Box(e);
box.start();
}, i * 500);
}
with()は金輪際あきまへん。ましてやこういう高度な使い方はamachangとかでないと出てきません。サイ本にもそう書いてある。
この場合はずっと素直に
Box.prototype.later = function(delay) {
var self = this;
setTimeout(function() { self.start() }, delay);
};
var nl = document.getElementById('target010').getElementsByTagName('div');
for (var i = 0, l = nl.length; i < l; i ++) {
var box = new Box(nl[i]);
box.later( i * 500 );
}
と書いた方がはるかにわかりやすい。
JavaScriptに限らず、ユーザーが使いそうな機能は、ユーザーに実装させるのではなくクラス/プロトタイプ側で用意しておくべし。
無名関数を紹介しとこうよ
使い捨て変数名前空間
(function() {
var nl = document.getElementById('target011').getElementsByTagName('div');
for (var i = 0, l = nl.length; i < l; i ++) {
var e = nl[i];
with({e:e})
setTimeout(function() {
var box = new Box(e);
box.start();
}, i * 500);
}
})();
ここまで来たら、やはり無名関数まで紹介して欲しかった。
(function(id, tag, interval){
var nl = document.getElementById(id).getElementsByTagName(tag);
for (var i = 0, l = nl.length; i < l; i ++) {
var box = new Box(nl[i]);
box.later( i * interval );
}
})('target10', 'div', 500);
こうしておけば、HTMLデザイナーとの連携も楽で、
(function(id, tag){
var nl = document.getElementById(id).getElementsByTagName(tag);
for (var i = 0, l = nl.length; i < l; i ++) {
var box = new Box(nl[i]);
box.later( i * interval );
}
}) // ここまで変更しないで!
(
'target10', // 要素のid
'div', // 要素内のタグ名
500 // スタートの間隔をミリ秒で指定して
);
と出来るし。
余談だけど、JavaScriptは無名関数で再帰が出来る。これが出来るのは、私が日頃使っている範囲ではPerl 6 と R ぐらいだ。
単にスコープを重ねるためだけに使うにはあまりにもったいないではないか。
まとめ
そろそろ JavaScript Best Practices が欲しいなあ。Perl 5よりも玉石混淆度が激しいし....
Dan the JavaScripter
この記事へのトラックバックURL
ArrayやDateなども含めた全てのオブジェクトに継承されるのでObject.prototype.addPrototypesな実装だとちょっとマズいケースがありますよ。
prototype.jsも昔はObject.prototype.extendな実装だったのをver1.4.0あたり(この辺ははっきり覚えてない)から単なるObject.extendに変更したという経緯があります。
親オブジェクトのプロトタイプにアクセスできなってしまう
}{
親オブジェクトのプロトタイプにアクセスできなくなってしまう
}
ありがとうございました。
Dan the Typo Generator
でも、堂々と恥晒せていられるアンタは立派だよな。
いずれにせよ、楽しませていただきました。
3628800
頑張れば、pythonでも。
人の書いたものに文句つけるなら、せめてもう少し理解度上げてください。
誰にプログラムを書くスタイルのどうこうを言える権利あるんだ?
おまいは教科書そのまんまコピーするのか?!