はじめに
突然ですが、Javaちょっと書いたことがある程度の若造が、ひょんなことからJavaScriptを書き始めたそうです。
その時に躓いたこと・気になったことがあるとのことなので、徒然なるままに書こうと思います。
JavaScriptの「オブジェクト型」とやらが複雑(当社比)だけどめっちゃ便利
自分がはじめてJavaScriptに出くわした時、まず「なんだこいつ?!」って思ったのがオブジェクト型なるものです。
オブジェクトを宣言する
まず簡単にオブジェクト型を宣言してみます。
var obj = {a : 'hoge'};
例えばこんなかんじです。簡単ですね。
ドットアクセスとハッシュアクセス
先程宣言したobjの値にアクセスする場合は、、、
console.log(obj.a); // hoge
console.log(obj[a]); // hoge
といった2種類のアクセス方法があって、上がドットアクセス、下がハッシュアクセスです。
当初は「なんでアクセス方法2通りもあるの?!」と思ったのですが、今となってはどちらも大好きです。
var message = {'button' : 'push', 'alert' : 'show', 'photo' : 'take'};
唐突ですが上記のようなオブジェクトがあったとします。pushという文字列を取り出したいのであれば、、、
console.log(message.button); // push
console.log(message['button']); // push
といった形で記述します。
正直このレベルだと、どっちのアクセス方法を使ってもいいかなと思うのですが、例えば下記のような場合はどうでしょう。
var obj = {
message : {'button' : 'push', 'alert' : 'show', 'photo' : 'take'},
fnc : function (/* string */ label) {
return this.message[label];
}
}
console.log(obj.fnc('alert')); // show
obj.fncにlabelを渡し、そのlabelに応じて値を返します。
この場合、fncが受け取る引数labelは可変値なのでハッシュアクセスの出番となります。
ちなみに、ここで間違えてドットアクセスで書いてしまうとundefinedが出力されます。
var obj = {
message : {'button' : 'push', 'alert' : 'show', 'photo' : 'take'},
fnc : function (/* string */ label) {
return this.message.label;
}
}
console.log(obj.fnc('alert')); // undefined
理由は簡単で、
var obj = {
message : {'button' : 'push', 'alert' : 'show', 'photo' : 'take'},
fnc : function (/* string */ label) {
return this.message['label'];
}
}
console.log(obj.fnc('alert')); // undefined
と書いているのと同じ意味になり、messageがlabelに対するvalueを持っていないのでundefinedが出力されます。
慣れてしまえばハッシュアクセスはとても便利で、オブジェクトが保持しているマップを次々に参照して対象のデータを引っ張り出すことも容易です。
var obj = {
mapping : {b : 'button', a : 'alert', p : 'photo'},
message : {'button' : 'push', 'alert' : 'show', 'photo' : 'take'},
fnc : function (/* string */ key) {
return this.message[this.mapping[key]];
}
}
console.log(obj.fnc('p')); // take
ハッシュアクセス、便利ですね。(ドットアクセスも好きです)
#比較演算子たちがいい仕事をしてくれる
比較演算子を使うとすごい便利な書き方ができるのですが、初めてその記法に出会ったときは「何だこれ?」状態でした。
とりあえずご覧ください。
var obj = {
mapping : undefined,
message : {'button' : 'push', 'alert' : 'show', 'photo' : 'take'},
error : 'error'
setMapping : function (/* object */ mapping) {
this.mapping = mapping;
},
fnc : function (/* string */ key) {
return this.message[this.mapping && this.mapping[key] || key] || this.error;
}
}
console.log(obj.fnc('button')); // push ---①
console.log(obj.fnc('chicken')); // error ---②
obj.setMapping({b : 'button'});
console.log(obj.fnc('b')); // push ---③
console.log(obj.fnc('button')); // push ---④
console.log(obj,fnc('a')); // error ---⑤
当時の自分は全く理解できませんでした。
return this.message[this.mapping && this.mapping[key] || key] || this.error;
結局なにをreturnしてるのでしょうか。。。
this.mapping && this.mapping[key] || key
まずここから。
比較演算子は左から順番に判定が行われますが、上記の場合はまず this.mapping の真偽判定が行われます。
setMappingを行う前の初期状態(①②)では this.mapping は undefined なのでここは false です。
よってその次に書かれている this.mapping[key] はスキップされ、 key が最終的に生き残ります。
return this.message[key] || this.error;
ここも先程と同じです、まず this.message[key] の真偽判定が行われます。その結果がfalseであれば、 this.error が生き残ります。(②③)
慣れれればとても便利な武器となるこの記法ですが、ときにはバグを生む原因となります。
var obj = {
mapping : undefined,
message : {0 : 'push', 1 : 'show', 2 : 'take'},
error : 'error'
setMapping : function (/* object */ mapping) {
this.mapping = mapping;
},
fnc : function (/* string */ key) {
return this.message[this.mapping && this.mapping[key] || key] || this.error;
}
}
obj.setMapping({z : 0, o : 1, t : 2});
console.log(obj.fnc('z')); // error
上記の例、 {z : 0} というマッピングを食わせているので、出力はpushかと思われますが、落とし穴があります。
this.mapping['z'] = 0
これが原因です。というのも 0 はfalse判定されてしまうからです。
return this.message[this.mapping && this.mapping[key] || key] || this.error;
ここでは this.mapping → true / this.mapping[key] → false / key → true と判定され、 this.message[key] → false / this.error → true と判定され、出力は想定しているものとは異なるものになってしまいます。
自分は上記の罠(?!)にはまりまくって頭をたくさん悩ませていたので、皆さんはぜひ気を付けてください。
#最後に
JavaScriptって奥が深いなと思う今日この頃ですが、自分がJavaScriptをはじめた時に「???」となった部分を皆さんに少しでも紹介できたらと思い書かせてもらいました。
熟練の方々には全然物足りない内容なのかなとも思いますが、プログラム初心者でこれからJavaScriptを始める方にとって少しでも役に立つ記事になればいいなと思います。