Douglas Crockford「JavaScript the Good Parts」学習メモ

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

 

モナドについては完全な理解ができなかったので割愛する。別途勉強しなおす必要がありそうだ。