JavaScriptでプロダクトコードを書くようになるとコードレビュー等でArray.lengthは気をつけるよう 言われることがあります。そこで、安心して使いたいので少し調べてみました。

結論

配列の要素数を変更させる操作は、Array.push等のArray.prototypeで用意されているメソッドを利用しよう!

なぜか?

JavaScriptのArray.lengthは配列の要素数を取得できますが、厄介なことに要素数と言いながら 実際は「最後のインデックス+1」を戻すプロパティです。

これは、インデックスが0から始まる、連続した配列だとlenghtは想定通りの値を取得できますが、
インデックスが0ではない場合やインデックスが不連続な配列だと想定外の値を戻す不具合の原因を
引き起こすことがあります。

  // インデックスが0でない場合
var arr = [];
arr[1] = 1;
console.log(arr.length); //-> 2
// インデックスが0でない場合(delete)
var arr = [1,2,3,4];
console.log(arr.length); //-> 4
delete arr[0];
console.log(arr.length); //-> 4
console.log(arr); //-> [1: 2, 2: 3, 3: 4]
// インデックスが不連続な場合
var arr = [1,2,,4];
console.log(arr.length); //-> 4

検証する

一方、Array.prototypeで用意されているメソッドを使うと、lenghtを自動調整してくれます。 これは言語仕様でカバーされている部分なのですが、ES6で追加されたProxyを介するとlengthが設定されるタイミングを わかりやすく見ることができます。

例えば上の例では、delete arr[0]で最初の要素を削除しましたが、その代わりにarr.shift()を使えば良いでしょう。 Proxyを経由して動作を確認してみます。

  var arr= [1,2,3,4];
// arrのプロパティ操作をトラップするProxyを作成する
var p = new Proxy(arr, {
set: function(target, property, value, receiver){
console.log(` set: property=${property}, value=${value}`);
target[property] = value;
return true;
},
get: function(target, property, receiver){
let value = target[property];
console.log(`get: property=${property}, value=${value}`);
return value;
}
});
// Proxy経由でshift()を呼び出す
p.shift();
console.log(arr.length); //(4)
console.log(arr);

// ** 結果 **
//-> get: property=shift, value=function shift() { [native code] } # shiftを呼び出す
//-> get: property=length, value=4 # arr.lengthを呼び出す(length=4)
//-> get: property=0, value=1 # arr[0]を取得(shiftの戻り値用と推測)
//-> get: property=1, value=2 # arr[1]を取得
//-> set: property=0, value=2 # arr[0] = 2(arr[1])を実行
//-> get: property=2, value=3 # arr[2]を取得
//-> set: property=1, value=3 # arr[1] = 3(arr[2])を実行
//-> get: property=3, value=4 # arr[3]を取得
//-> set: property=2, value=4 # arr[2] = 4(arr[3])を実行
//-> set: property=length, value=3 # arr.length = 3 を実行
//-> [2, 3, 4]

ただshiftを呼び出しただけですが、処理の最後に新しいlengthをセットしたりと内部で処理されているのが わかるかと思います。ついでにshiftでは配列を一つ一つ入れ直す処理が発生していますね。。。

なお、上記はChrome(56.0.2924.87 (64-bit))で確認しました。現時点ではFirefox(51.0.1 (64 ビット))でも同じ出力となりました。

結論

ということで安全に使うためにはArray.prototypeで用意されているメソッド群を使いましょう!