うさぎともふもふしたりうさぎともっふもっふしたり。プログラミングしたり。
2010-04-27
細かいJavaScriptの仕様や習慣やテク集
気づいたことやミスしたことなどメモしていたので確認作業。細かい仕様だったり暗黙のルールだったり、テクニックだったり。JSに慣れていたら当たり前なことばかりかもしらん。
追記のところはid:os0xさんより。ありがとうございます!
undefined
var a; alert(a) // undefined
宣言だけだとundefined。undefinedというのは、宣言している変数に値が入っていませんよ、ということ。
そしてオブジェクトには無いプロパティとか参照すると出てくる。
var a = 100; alert(a.length); // undefined
さらに引数も。
function func(val) { alert(val); } func() // undefined
引数はCallオブジェクトってのに格納される。このオブジェクトはローカルの変数が格納されるオブジェクトでもある。変数宣言されたようなもんだから引数もundefinedになるんだろうね。
変数宣言の場所
変数宣言は関数の先頭というのがJSでは一般的。ブロックスコープないので。あとは、こういうミスの防止。
var a = 'global'; var func = function() { alert(a); var a = 'local'; }; func(); // undefined
これは、
var func = function() { var a; alert(a); a = 'local'; };
こうなっている。とはいえこういったミスはそうしない。
データの型
変数の型はないけどデータの型はある。
- 基本型
- 文字列
- 数値
- 論理値
- null
- undefined
- 参照型
- 関数
- オブジェクト(配列もここ)
文字列に関しては参照型という話もあるけど、人間が扱うに分に関しては基本型という認識で問題ない。
null値については、そのオブジェクトに値が無いという意味。undefinedと混同してしまうなこれ。
undefinedっていうのは未定義値って訳されている。だからundefinedは値が変数に設定されていない、定義されていないときなどに返ってくるもので、nullは値は定義されているんだけど(オブジェクトがない(値がない)ってことらしい)、その値が有効な値でないときに返ってくるもの、と私は理解している。
nullがよく返ってくる例としては、存在しない要素を参照しようとしたりすると返ってくる。
console.log(document.getElementById('hentainohanazono')); // null
id='hentainohanazono'の属性をもった要素があればnullじゃないけど。ヘンタイな人には馴染み深いid名ですね。wrapperの次に定番ですね。素晴らしいですね。
それからunllとundefinedは同じものでない。だのに等値演算子で比較するとこうなる。
null == undefined // true
でもこれは間違い。同じではない。だからこういう比較をしたいときは同値演算子にする。
null === undefined // false
面倒な暗黙の仕様。結構ひどいトラップ。こういう面倒なことが他にもあったりするので、JSでは同値演算子使えとよくいわれる。
それからnullは0ではない。でも数値に変換すると0にはなる。
Number(null) // 0 //比較 null == 0; // false
しかし比較するとfalseになる。実際はnullと0は同じでないので注意。
またnullはオブジェクト型。
typeof null // 'object'
nullやundefinedを区別する場合はtypeof演算子や同値演算子(===)を使う。
速い型変換
見辛いがこのやり方が一番速い。
var a = '12'; alert(typeof a+':'+a); // string:'12' alert(typeof +a+':'+(+a)); // number:12 alert(typeof !!a+':'+(!!a)); // boolean:true
ただ読みづらくなる。コメント入れるのが良いかも。そこまで速さにこだわらんならparseInt使うかな。Numberは遅い。
parseInt
つながりで。基数を指定すると面倒なことならない。
parseInt('08', 10); // 8 parseInt('08'); // 0
配列のコピー
参照渡しだから代入しただけではコピーされない。また関数の引数でも参照が渡されているだけ。ということで、配列のコピーって結構面倒なことだったりする。普通に代入すると、
var alpha = [1,2,3,4,5,6]; var func = function(arr){ arr.pop(); return arr; }; alert(func(alpha)); // 1,2,3,4,5 alert(alpha); // 1,2,3,4,5
元の配列まで変わる。参照だもの。
だから値をコピーしる。
var alpha = [1, 2, 3, 4, 5, 6]; var copy = []; var func = function(arr){ for(var i = 0; i < arr.length; i++) { copy[i] = arr[i]; } copy.pop(); return copy; }; alert(func(alpha)); // 1,2,3,4,5 alert(alpha); // 1,2,3,4,5,6
さらに改良。
Array.prototype.arrayCopy = function() { return Array.apply(null,this) }; var alpha = [1, 2, 3, 4, 5, 6]; var func = function(arr){ var copy = arr.arrayCopy(); copy.pop(); return copy; }; alert(func(alpha)); // 1,2,3,4,5 alert(alpha); // 1,2,3,4,5,6
追記
concatも良さげです。
var a=[1,2]; var b=a.slice(); a.pop(); alert([a.length,b.length]);//1,2
三項演算子を見やすく
あまり好きではありませんが短くすむなら。
var x = 85; var y = x > 70 ? '上':'下';
でもこれじゃあ見辛い。なので、
var x = 85; var y = (x > 70) ? '上':'下';
こうする。代入せず式に含めるときは、
((x > 70) ? '上':'下');
括弧でくくる。まあ人それぞれ。
with文でブロックスコープ
with文は利用しないというのがJSでは一般的に知られている。だから使わなくても良いかもしれない。ただし時と場合によるんじゃないかということで使い方だけ覚えるようにした。
発展として、with文でブロックスコープができる。
with({i:0}) { i = 1; } alert(i); // エラー
少し難しい。無名のオブジェクトに値を代入している。
これを利用するとクロージャとなる。
for(var i = 0, func = []; i < 5; i++){ with({i:i}) func[i] = function() { return i; } } alert(func[0]()+','+func[1]()+','+func[2]()); // 0,1,2
ちなみにwithをとると、
for(var i = 0, func = []; i < 5; i++){ func[i] = function() { return i; } } alert(func[0]()+','+func[1]()+','+func[2]()); // 5,5,5
結構前からあるテクニックみたいですが、あまり知られていなかったり。でも、そもそも使うときがあるかなというのはまた別の話。どっかのコード読むときには役立つかもしれない。
Argumentsオブジェクト
関数の引数情報を管理するオブジェクト。利用する際はargumentsと小文字で。argumentsプロパティは扱いが配列みたいだけど配列そのものではない。「配列に似せている」だけ。
var func = function() { alert(arguments[0]); } func(1); // 1;
引数を設定していないけどいける。
var func = function() { var leng = arguments.length; for(var i = 0, arr = []; i < leng; i++){ arr[i] = arguments[i] * 10; } alert(arr); }; func(1,2,3,4,5,6); // 10,20,30,40,50,60
argumentsはソース読み泣かせで最初はよく混乱した。なぜそこに引数があるのだ桜木花道ぃいい、みたいな。
calleeプロパティ
Argumentsオブジェクトにはcalleeプロパティというのもある。再帰ができる。
var func = function(n) { return (n != 0) ? n * arguments.callee(n-1) : 1; }; alert(func(7)); // 5040
階乗。
参照の優先順位
分かりきったことといえばその通りなのですが。
Object.prototype.name = 'object'; var Parent = function() { this.name = 'parent'; }; Parent.prototype.name = 'parent_prototype'; var child = new Parent(); child.name = 'child'; alert(child.name);
順位としては、
1.child
2.parent
3.parent_prototype
4.object
の順位。prototypeはメソッド追加するぐらいだろと思っていて、そのとき読んだコードにプロパティがいろいろ混じっていて混乱した。初期化しない値もあるものね。prototypeはあくまでも最終の参照。当たり前といえばそうだけど分からなかったなあ。
オブジェクトで配列
JSの配列はオブジェクトを拡張したものっていわれている。だから遅いらしい。で、どういう形かなと。
arr = { 0 : 'zero', 1 : 'ichi', 2 : 'ni', 3 : 'san', length: 4 }; arr.__proto__ = Array.prototype;
配列というのはオブジェクトをこんな感じにしたものなんだと思う。
alert(arr[0]+', '+arr['0']) // zero,zero //関数 alert(arr.reverse()); // "san", "ni", "ichi", "zero"
配列の関数もこのように扱える。ただしfor in文だとlength出てきちゃうんだよなぁ。やっぱり違うのかな?
ということであっているか心配になって検索。
http://d.hatena.ne.jp/amachang/20070202/1170386546
コードまんまか。うーん、でも配列っぽく扱えるレベルってあたりか。オブジェクトにlength与えるとそれっぽく扱えるんでしょう。2007年の時点なので、なんか過去をなぞっているのが切なくなった。
よし、今度amachangさんの過去記事ストーキングしてくる。夜道に武装してほふく前進しながらモルヒネ片手にニヤついている奴がいるかも知らんぜ。
追記
配列のlengthにはDontEnum属性というものが設定されているのでfor inでは出てこないようになっています。ECMA-262 3rdではDontEnum属性をJavaScriptから操作することはできないので(ECMAScript5では可能になりましたが)、オブジェクトで完全な配列を再現することはできません。他にも、
var a=[];a[100]=1;alert(a.length);//101
このように配列は要素を入れるとlengthは自動で設定されますが、この挙動を再現することができません。
ちなみに最近のエンジンならもちろん配列のアクセスを最適化しているので、配列が遅いということはありません。
クラスっぽいもの
JSにはクラスがないけどクラスっぽいものはある。というかオブジェクトなのですが。パっと見て分かりづらいので、JSではクラスっぽいものの名前の先頭は大文字にするルールみたいなのが定着している。
var Klass = function(x,y){ this.prop1 = x; this.prop2 = y; }; var ins = new Klass(2,2);
そしてこれはクラスっぽいものと言うと面倒なのでコンストラクタと言う。また人によってはクラスともいう。
実は正確な定義がない。なんでクラスがないのにクラスっていうかというと、説明するときに名称がないと説明しづらいからなんだろう。だからクラスってことにしたんだね。このあたりの定義の曖昧さは混乱の種。
今のJSにはクラスは存在しないので名称としてはコンストラクタで良いと思う。今後どうなるかわからないけど。人それぞれ言い方が違いますが、今のJSでクラスとコンストラクタは同じものを指しているよ。
それからfunction。もう見慣れてしまった。このあたりも混乱の種だよね。コンストラクタと関数の見分けが最初は辛い。大文字、thisが使われている、prototypeが使われている、このあたりでコンストラクタかどうか判断する。
追記
コンストラクタの見分けはnewされてるかどうかってのが重要。書いてなかった私はバカか。
thisキーワード
thisは少々やっかい。
var prop = 'global'; var Klass = function(){ this.prop = 'prop'; this.func = function() { alert(this.prop); (function() { alert(this.prop) })(); } } var ins = new Klass(); ins.func(); // prop global
メソッドの内部関数ではthisがグローバルに羽ばたきやがる。実は関数内でthisを指定するとグローバルオブジェクトがセットされる。
function func() { alert(this); // [object Window] };
うぬぅ。というわけで内部関数でその親オブジェクトを指定するためには、
var prop = 'global'; var Klass = function(){ this.prop = 'prop'; var self = this; // せるふりゃー this.func = function() { alert(this.prop); (function() { alert(self.prop) })(); } } var ins = new Klass(); ins.func(); // prop prop
せるふりゃー。こうすることで自分自身を指している状態のthisを保存するようにする。
ここでthis入れたらselfはwindowが代入されているんじゃないのとか、this.propてwindow.porpにならないの?とか思っちゃうかもしれないけど、newされていることを忘れない。
インスタンス化すればthisはそのオブジェクト自身がセットされることになる。
だのに内部の関数のスコープに入るとグローバルにイン・ザ・スカイ!この関数のthisについては仕様ですと納得することに。言語設計のミスなのかどうかしらないが仕様は正義。だがあえて言おう。カスであると!(ガンダム芸人見て言いたかった)
これは簡単に回避できるのでそこまで頭痛めるようなことじゃない。
追記
thisは「一番内側の関数がどのように呼び出されたか」という条件のみによって決定されます。
>メソッドの内部関数ではthisがグローバルに
というあたりは誤解を招くかなと思います。
オブジェクトのメソッドとして呼び出されればそのオブジェクトがthisになり、call/applyで任意のオブジェクトにもできるというだけです。
var A={name:'A',f:function(){alert(this.name)}};
var B={name:'B'};
A.f(); // A
A.f.call(B);// B
ただ、関数の呼び出され方が特殊な場合(Event周りなど)ではそれに応じたthisが設定されるので、このあたりが少々ややこしいですね…
ちょっと狭い範囲でのthisのことをうじうじと書いてしまったようで。
obj={x:y}とobj.x=yの違い
恥ずかしながらこの違いに私は最近気づいた。違いもクソも別ものなんだけど。prototypeをほとんど使ったことないからわからないもんだな。簡単にいえばobj={}の{}はnew Objectのシンタックスシュガーだから、
var obj1 = {}; var obj2 = new Object();
同じ意味。新しくオブジェクトを生成しているんだよね。
ここまでは私も理解していた。でも、
var obj1 = { prop1:'prop1' }; var obj2 = obj1; obj2.prop2 = 'prop2'; alert(obj2.prop1); // prop1 obj2 = {prop:'prop2'}; alert(obj2.prop1); // undefined
こうしてコピー(参照値の代入)がからんだら・・・あれれ?となった。そして、prototypeがからんでくるとさらにややこしいことになる。複雑なので日本語で。
var Obj = function(){}; Obj.prototype = { prop1:'1つめ' }; var obj1 = new Obj(); alert(obj1.prop1); // 1つめ Obj.prototype = { prop1:'2つめ' }; alert(obj1.prop1) // 1つめ var obj2 = new Obj(); alert(obj2.prop1) // 2つめ Obj.prototype.prop1 = '3つめ'; alert(obj1.prop1) // 1つめ alert(obj2.prop1) // 3つめ
この場合prototypeは暗黙の参照をする以外はただのオブジェクトだってこと、オブジェクトのコピーは参照渡しだということ、{}はnew Object()のシンタックスシュガーであること、obj.x=yは既存オブジェクトへの付け足しだということ、ということが理解できていれば納得できることだけどそこら辺が曖昧だと良く分からない。
obj={x:y}とobj.x=yは普通に違うのですが同じようなものとして認識してしまった言い訳をすると、入門書では、
var obj = {}; obj.prop1 = 'prop1';
と、
obj = { prop1:'prop1' };
は一緒だよと紹介されているので、そこから何を間違ったか区別がつかなくなったのです。
これだと確かに一緒なんだけど、obj={x:y}はobjという変数に新しいオブジェクトを代入していて、obj.x=yは既存のオブジェクトにプロパティを付け加えていることになるってのが良く分かっていなかったらしい。
こういうことはよくある。もともとは理解しているものでも、ちょっと形が変わったり考えたことがなかったものだと分からなくなったり。
単にお前がバカなんだといえばそうなんだけどこのやろうバズーカで星にするぞ☆
反省会
そろそろ実践的にがんばろうか。
- 1085 http://b.hatena.ne.jp/
- 623 http://b.hatena.ne.jp/hotentry
- 413 http://reader.livedoor.com/reader/
- 327 http://b.hatena.ne.jp/hotentry/it
- 275 http://blog.livedoor.jp/dankogai/archives/51439807.html
- 177 http://blog.livedoor.jp/dankogai/
- 171 http://www.google.co.jp/reader/view/
- 160 http://pipes.yahoo.com/pipes/pipe.info?_id=faa858a20082ef6d25ad27557e37e011
- 149 http://www.sleipnirstart.com/
- 130 http://www.google.com/reader/view/
