#はじめに
友人がJavaScript書いてて辛そう楽しそうなので、私が書いてて気をつけている点とかその他引っかかりそうな所をまとめてみた
また実行環境は Windows10 / GoogleChrome 最新版
#バージョン
前提としてJavaScriptのバージョンをどうするか
ES6ではクラス構文とかが追加されてだいぶ書きやすくなった
一方ES5までしか対応していない環境もある
とはいうものの、メジャーなブラウザはほぼES6に対応している
またどうしてもES5が必要な場合でも、Babel先生に頼めばES5に落としてくれる
という訳でES5で書く必要はほぼ無いと思う
以降ES6を前提に書いていく
#変数宣言
ES5まではvarしか無かった
ES6でconstとletが登場したので、これらの違いについて見てみる
function sake() {
sake0 = 'Shinomine';
var sake1 = 'Kujira';
}
sake();
console.log(sake0); // Shinomine
console.log(sake1); // sake1 is not defined
varが無いとグローバルスコープになる
varを付けるとスコープは関数内
次にletについて
if (true) {
var sake2 = 'Tsukasabotan';
}
if (true) {
let sake3 = 'Senchuhassaku';
}
console.log(sake2); // Tsukasabotan
console.log(sake3); // sake3 is not defined
varでもifは通り抜けるが、letはifの中にスコープが収まっている
もう一つvarとletの違いがある
var sake4 = 'Harushika';
var sake4 = 'Akishika';
console.log(sake4); // Akishika
let sake5 = 'Katanosakura';
let sake5 = 'Michizakura'; // Identifier 'sake5' has already been declared
varは上書き宣言できるがletはできない
なおletとconstは値が上書きできるかどうかの違いだけ
以上より、letとconstだけで済ませるべきだと思う
#比較
等価演算子には==と===の二つがある
後者のほうが厳密であるが、実際どう違うか少し見てみる
1 == 1 // true
1 == '1' // true
1 == true // true
こちらは大体等しければtrueを返す
1 === 1 // true
1 === '1' // false
1 === true // false
こちらは厳密に一致した場合だけtrueを返す
前者は左右を同じ型に変換してから等しいかどうかを調べているらしい
後者なら形が違った時点でfalseを返す
なお、否定の場合は!=と!==のようになる
気をつけたい比較の一つにNaNがある
NaN === NaN // false
NaN同士の比較はfalseになる
この場合Number.isNaNを使う
Number.isNaN(NaN) // true
なお、Number.isNaNはES6から実装された
それ以前はisNaNを使用する
が、これがまた問題がある
Number.isNaN(null) // false
Number.isNaN(undefined) // false
isNaN(null) // false
isNaN(undefined) // true
isNaN(undefined)がtrueを返しているので注意
#nullとundefined
似ているようで違う
まず比較すると以下のようになる
null == undefined // true
null === undefined // false
やはり似て非なるらしい
では何が違うのかを見ていこう
まずはundefinedから
let sake;
console.log(sake); // undefined
sake = 'Akabu';
console.log(sake); // Akabu
console.log(sake.alcohol); // undefined
宣言だけされた変数にはundefinedが入っている
同じく初期化していないプロパティもundefined
function manotsuru() { }
console.log(manotsuru());
何もreturnしない関数の場合、戻り値がundefinedになる
returnだけ書いた場合も同様
このように、undefinedとはまだ作られていない状態、つまり未定義である
一方のnullだが、これは明示的にnullを使用しないと発生しない
let sake = 'Daimon';
console.log(sake); // Daimon
sake = null;
console.log(sake); // null
一度定義したものを空っぽにするときに使う
ニュアンス的な違いになるが、nullは「空っぽ」、undefinedは「未定義」と考えると良いと思う
ではこれらの使い分けをどうするかだが…実際の所あまり違いはないと思う
ただし、nullは明示的な空であるのに対して、undefinedは自然発生的な空とも取れる
そのため、変数を空にする目的でundefinedを代入すると以下のようになる
let sake0 = 'Takesuzume';
let sake1 = 'Gassan';
// ---- この間非常に長いコード ----
sake0 = undefined;
sake1 = null;
// ---- この間非常に長いコード ----
console.log(sake0); // undefined
console.log(sake1); // null
前者は空なのだが、これが単なる未定義なのか、使用した後に消されたのかがわからない
後者は明示的に空になったのがわかる
以上より、明示的に空にするならnullを使用するべきで、undefinedを代入するのは避けたほうが良いと思う
同じことは関数にも言える
function okura(canGet) {
if (canGet) return 'Okura';
return undefined;
}
function sennorikyu(canGet) {
if (canGet) return 'Sennorikyu';
return null;
}
console.log(okura(false)); // undefined
console.log(sennorikyu(false)); // null
こちらも後者なら明示的に空だとわかるだろう
#名前空間
javascript に名前空間なんてものはない
だが、それ風味なものは実装できる
一つだけグローバルなオブジェクトを用意しておいて、残りはそいつのプロパティとして実装する方法である
Sake = { };
Sake.Dewazakura = class { } // OK
Sake.Kokushimusou = class { } // OK
なお、名前空間自体が競合した場合を考えて以下のように書くのが良いらしい
var Sake = Sake || { };
この場合はvarでないとエラーになるようだ
#クラス宣言
クラスの宣言方法は二種類ある
class Ippongi { }
Suginishiki = class { }
これだけならほとんど違いはない
しかし、先述の名前空間の兼ね合いで、後者の書き方をしたいと思う
var Sake = Sake || { };
class Sake.Bijofu { } // Unexpected token .
Sake.Urakasumi = class { } // OK
もちろん関数も同じ
function Sake.Umenoyado() { } // Unexpected token .
Sake.Shirakiku = function() { } // OK
#関数を返す
Box = class {
constructor() {
this.sake = ['Reisen', 'Suigei', 'Tengumai'];
}
show() {
this.sake.forEach(a => console.log(a));
}
getShow0() {
return this.show;
}
getShow1() {
return () => this.show();
}
getShow2() {
return function() { this.show() };
}
}
const box = new Box();
box.show(); // Reisen Suigei Tengumai
getShowを三つ用意した
一つ目は普通にメソッドを返す、二つ目は無名関数で包んで返す
三つ目は後述する
const boxShow0 = box.getShow0();
boxShow0(); // Cannot read property 'sake' of undefined
メソッドをそのまま返すと実行できない
const boxShow1 = box.getShow1();
boxShow1(); // Reisen Suigei Tengumai
こちらは通る
メソッドを返すだけでは実行できず、メソッドを実行する関数を返す必要がある
しかしこれが可能なのはアロー関数を使用した場合に限る
ここで、アロー関数ではなく普通の関数宣言した場合の三つ目のshowを見てみる
const boxShow2 = box.getShow2();
boxShow2(); // Cannot read property 'show' of undefined
これはアロー関数の特性の一つで、アロー関数は宣言した時点でthisの値が確定する
逆に言うと、getShow0やgetShow2のthisは宣言した時点で確定していない
これらは呼び出された時点でその左辺を見て決定されるらしい
つまりboxShow0やboxShow2は左辺が無いためthisがおかしくなったようだ(この時のthisはグローバルオブジェクトを指している…はず あまり確かでない)
#あとがき 前編
まだまだ書ききれないほど罠が散らばっているが、今回はここらへんで終わりとしておく
全部書いているとあまりにも多すぎるので、特に引っかかる可能性が高そうなポイントに絞った
なお、敢えて最後に説明すると、私見が混ざっている文章の語尾には「思う」と付けた
あくまで私はこう思ったという部分である
正直それ以外の部分はググればいくらでも出てくるし、この記事はその寄せ集めに過ぎない
だが、この「思う」部分は私にしか語れない
なので、「思う」の部分を取捨選択しながら読んでいただけるとこの記事の意味が出てくると思う
ちなみにこの説明を最後に持ってきたのは、一回目読んでる最中に余計な思考を入れて欲しくなかったからだ
読み終わった上で取り入れるかどうかもう一度考えてほしい
#あとがき 後編
このソースコードはノンフィクションですが、実在する酒とは関係ありません
関係ありませんが、酒は美味いです