Array.prototype.slice についての考察(というか妄想)のはなし

関数の引数は、配列っぽいけど配列じゃないため扱いにくいから、配列に直す場合があって

function sortedArrayFromArgs () {
	var slice = Array.prototype.slice;  // (1)
	var args  = slice.apply(arguments); // (2)

	args.sort(function (a, b) {
		return (a === b) ? 0 :
		       (a < b)   ? 1 : -1;
	});
	return args;
}
var $sorted = sortedArrayFromArgs(-123, 123, 345, -987);
console.log($sorted); // [ -987, -123, 123, 345 ]

上の例だと、可変長の引数をソート済みの配列に直して返しています。
この時に (1) と (2) で、配列に直すテクニックは、結構知られているけど、どうして、関数の引数が配列になるのかは、あまり紹介されていませんよね。


ということで、以下は考察

  • 1) slice メソッドは、呼ばれた時に空の配列を作ります。そして、this の指す配列の一部(引数を指定しないか、0 を指定すると全部)をコピーする
  • 2) Array.prototype.slice 関数は this が指すオブジェクトは配列以外でもOK. 乱暴な言い方すると「配列っぽい実装のオブジェクト」ができていればいいらしい。
  • 3) Arguments オブジェクトは、この「配列っぽい実装のオブジェクト」に該当するので、 this の代わりに arguments を適用することで( (2) の個所)配列になる。

という寸法らしい。


ということで、「配列っぽい」オブジェクトを作ってためしてみたらどうか? という実験


かなり無理やりな実装方法にしてみた

function LikeArray (first, second, third) {
    this[0] = first;
    this[1] = second;
    this[2] = third;
}
(function (P) {
    P.length = function () {
        var i = 0;
        for (var p in this) {
            i++;
        }
        return i;
    }();

    P.slice = Array.prototype.slice; // thisの対象にちゅうもくしてね

})(LikeArray.prototype);

var likeArrayObject = new LikeArray('abc', "DEF", "kiss");
console.log(likeArrayObject); // { '0': 'abc', '1': 'DEF', '2': 'kiss' } (A)

var cloneLikeArrayObject = likeArrayObject.slice(0);
console.log(cloneLikeArrayObject); // [ 'abc', 'DEF', 'kiss' ] (B)

// 配列ならソートできるはず
console.log(cloneLikeArrayObject.sort(function (a, b) {
    a = a.toUpperCase();
    b = b.toUpperCase();
    return (a === b) ? 0 :
           (a < b)   ? 1 : -1;
}));  // [ 'kiss', 'DEF', 'abc' ]
  • (A): new LikeArray(...) したオブジェクトは「配列っぽいオブジェクト」で、"length" プロパティを持つ。また、"slice" メソッドを持つ
  • (B): sliceメソッドを使って「配列っぽいオブジェクト」を「配列オブジェクト」に直したもの

になった