Pluralsightの「JavaScript the Good Parts」(Douglas Crockford)の学習メモ。
同名の書籍(日本語版:「JavaScript: The Good Parts -『良いパーツ』によるベストプラクティス」)のまとめ版ビデオだろうか。著者ご本人によるセミナーを録画したもののようだ。
- 左波括弧"{"の前で改行しない
var incorrect = function() { return { key: 'value' }; } alert(incorrect()); //shows 'undefined'
上記はreturnのあとにセミコロンが補完されてしまっている。
- withステートメントは使わない
with (o) { foo = koda; }
これは、
o.foo = koda; o.foo = o.koda; foo = koda; foo = o.koda;
のどれで解釈されるかわからないので避ける。
- 変数宣言と初期化の誤用
(function() { var a = b = 0; })(); alert(b); //0
この変数宣言部は
b = 0; var a = b;
と同じであり、グローバルプロパティbが意図せず作成されてしまう。
(function() { var a = 0, b = 0; })(); alert(b); //Error(no variable
こう書くこと。
- インクリメント・デクリメント演算子
x++; y--;
この書き方ではなく、
x += 1; y -= 1;
「+=」「-=」を使うこと。
- ブロックは必ず波括弧でくくる
if (a) { doSomething(); }
ブロックはたとえ1行でも波括弧でくくる。
- 「constructor」プロパティに注意
オブジェクトはコンストラクタ関数への参照であるconstructorプロパティを持っている。
var word_count = []; function bump_count(word) { if(word_count[word]) { word_count[word] += 1; } else { word_count[word] = 1; } }
このコードは、wordが文字列「constructor」だと正常に動作しない。
var word_count = []; function bump_count(word) { if(typeof word_count[word] === 'number') { word_count[word] += 1; } else { word_count[word] = 1; } }
このように、型チェックを足す必要がある。
- プロトタイプから継承した関数はfor列挙に含まれる
for (name in object) { if(object.hasOwnProperty(name) { .... }
}
もしobjectがhasOwnPropertyプロパティを持っていると、上記コードは失敗する。
for (name in object) { if(Object.prototype .hasOwnProperty.call(object, name) { .... }
}
このように、hasOwnPropertyを確実に指定しなければならない。
- キーは文字列
var dict = []; dict['0'] = true; dict[1] = false; alert(dict[0]); //true alert(dict['0']); //true alert(dict[1]); //false alert(dict['1']); //false alert(dict[2]); //undefined
- 数字は第一級オブジェクト
if(!Number.prototype.trunc) { Number.prototype.trunc = function trunc() { return this >= 0 ? Math.floor(this) : Math.ceil(this); } } alert((1.5).trunc()); //1 alert((-1.5).trunc()); //-1
- NaNは特別な数字
NaN === NaN はfalse、NaN !== NaNはtrue。
NaNは何とも等しくはならない。
- 配列の注意点
配列はObjectであり、その添え字は文字列型に変換される。
他の言語のように、それほど効率的なデータ構造ではない。
for (v in ar) { .... }
順番が保証されないので、上記のようにforでの列挙は行わないこと。
ar.sort(); alert(ar); //15, 16, 23, 4, 42, 8 ar.sort(function(a, b){ return a - b; }); alert(ar); //4, 8, 16, 16, 23, 42
sort()はデフォルトでは文字列としてソートする。
- オブジェクトのtype
nullとundefined以外はすべてObject。しかし、「typeof null」は'object’を返すので注意する。
type |
typeof |
object |
'object' |
function |
'function' |
array |
'object' |
number |
'number' |
string |
'string' |
boolean |
'boolean' |
null |
'object' |
undefined |
'undefined' |
alert(typeof null === 'object'); //true alert(null === null); //true alert(typeof [] === 'object') //true alert(Array.isArray([])) //true
そのため、null判定には「null === null」、配列の判定にはArray.isArray()を使う。
- false扱いの値
false null undefined "" 0 NaN
- arguments
function sum() { var i, n = arguments.length, total = 0; for(i=0; i < n; i+=1) { total += arguments[i]; } return total; } var ten = sum(1, 2, 3, 4); alert(ten); //10
argumentsは配列風のオブジェクトで、関数の引数をすべて保持する。
- this
関数のみで実行されると、thisはglobalオブジェクトになる。
var fnc = function () { alert(this.name); }; function MyObject(name) { this.name = name; } MyObject.prototype.fnc = fnc; var myObject = new MyObject("my object"); fnc(); //shows global object name(such as window name) myObject.fnc(); //"my object"
内部関数は、外側のthisへアクセスできない。
var outerFnc = function() { function innerFnc() { alert(this.name); } innerFnc(); }; function MyObject(name) { this.name = name; } MyObject.prototype.outerFnc = outerFnc; myObject = new MyObject('my object'); myObject.outerFnc(); //shows global object name
外側のthisへアクセスしたい場合は、あらかじめ変数thatにthisを代入しておく。
var outerFnc = function() { var that = this; function innerFnc() { alert(that.name); } innerFnc(); }; function MyObject(name) { this.name = name; } MyObject.prototype.outerFnc = outerFnc; myObject = new MyObject('my object'); myObject.outerFnc(); //my object
- 高階関数
function memoizer(memo, formula) { var recur = function (n) { var result = memo[n]; if(typeof result !== 'number'){ result = formula(recur, n); memo[n] = result; } return result; }; return recur; } var factorial = memoizer([1, 1], function(recur, n) { return n * recur(n - 1) } ); var fibonacci = memoizer([0, 1], function(recur, n) { return recur(n - 1) + recur(n - 2) } ); alert(factorial(5)); //120 alert(fibonacci(5)); //5
- ループ内で関数は作成しない
function addHandlers() { for(var i=1; i<=4; i += 1) { var element = document.getElementById(i.toString()); element.onclick = function() { alert('id:' + i); }; } }
このコードだと、すべてのelementが「id: 5」をポップアップしてしまう。関数作成時(ループ完了後)に変数iの値が参照されるため。
function make_handler(element_id) { return function() { alert('id:' + element_id); } } function addHandlers() { for(var i=1; i<=4; i += 1) { var element = document.getElementById(i.toString()); element.onclick = make_handler(i); } }
別関数にすることで解決できる。
- カリー化
function curry(func, first) { return function(second) { return func(first, second); }; } var add3 = curry(add, 3); alert(add3(4)); //7 alert(curry(mul, 5)(6)); //30
ECMA6からは、可変長引数を次のように書ける。
function curry(func, ...first) { return function(...second) { return func(...first, ...second); }; }
- メソッド化
function methodize(func) { return function(x) { return func(this, x); }; } Number.prototype.add = methodize(add); alert((3).add(4)); //7 function demethodize(func) { return function(that, y) { return func.call(that, y); } } alert(demethodize(Number.prototype.add)(5, 6)); //11
- 他関数が一度のみ実行されることを保証した関数の例
function once(func) { return function(){ var f = func; func = null; return f.apply(this, arguments); } } add_once = once(add); alert(add_once(3, 4)); //7 alert(add_once(3, 4)); //throw!
- カウンタをインクリメント/デクリメントする関数の例
function counterf(a) { return { inc: function() { a += 1; return a; }, dec: function() { a -= 1; return a; } }; } var counter = counterf(10); alert(counter.inc()); //11 alert(counter.dec()); //10
モナドについては完全な理解ができなかったので割愛する。別途勉強しなおす必要がありそうだ。