ヨーキョクデイ

100% pure impurities, which may imply some value. (j は虚数単位)

配列の zip メソッドを作り、ECMAScript 6 やら、新しい JavaScript に触れてみた

ここにおける JavaScript とは Mozilla 界隈におけるそれのことである。
しばらく JavaScript から離れており、JavaScript 1.8 あたりからの新機能がわからなかったので、MDN を見ながらがんばってみた。
今回はなぜか Array オブジェクトに zip() メソッドを追加する必要があったので、RubyArray#zip の挙動を参考に作った。一般に、同様のメソッドなり関数なりは複数の配列の同一インデックスの要素を配列にし、それらの配列を要素とする配列を返すような挙動であるが、それぞれ長さが異なる配列を扱う際に、返す配列の長さを呼び出し元の配列の長さに合わせる挙動という意味で Ruby に合わせた。結果として不足分は undefined が入り、余った分は切り捨てられる。実行結果としては次のようになる。

[1, 2, 3].zip([4, 5], [6, 7, 8, 9]);
    // => [[1, 4, 6], [2, 5, 7], [3, undefined, 8]]

これを従来の JavaScript 1.6 で実装すると次のようになる。

Array.prototype.zip = function Array_zip(){
    var args = Array.slice(arguments);
    return this.map(function(e, i){
        return this.map(function(a){
            return a[i];
        });
    }, [this].concat(args));
};

可変個引数を扱うために arguments オブジェクトをごにょごにょし、Array#map() の第 2 引数ではコールバック関数内で this となるオブジェクトを渡したりしている。
さらに新しめの Firefox で動く JS ではこうなる。

Array.prototype.zip = function Array_zip(...args)
    this.map((e, i) => [this, ...args].map(a => a[i]));

これはまず、代入する関数が式クロージャなるものになっている。これは Firefox 3 で導入された JavaScript 1.8 の新機能で、return 文と波括弧を省略できるというものである。

さらに、この関数の仮引数で ...args となっているのは Firefox 15 で導入された rest パラメータなるものである。従来は配列のような arguments オブジェクトを使ったが、ここで得られる args配列であるため、Array.slice(arguments) あるいは Array.prototype.slice.call(arguments) のような小細工なしに引数リストを配列として得られる。ただし、function f(a, b, ...c){} のような場合で得られる cab が含まれないので arguments との使い分けが求められる。

また、Array#map() の引数として渡している関数は Firefox 22 で導入されたアロー関数なるものである。これは無名関数式の表記法の 1 つであるのだが、従来品との違いは、関数内で this の指す物が異なる、コンストラクタ関数になれない、ということらしい。
今回重要なのは this であって、誤解を恐れずに書けば、コールバック関数内で this は外側における this と同じ物のを指しているため、あらかじめ外側で var self = this; としてコールバック内で self を参照するという必要がない。従来品の例では [this].concat(args) という Array オブジェクトを Array#map() の第 2 引数で渡し、コールバック関数内での this としているが、var self = this; しておき、コールバック関数内で [self].concat(args).map( ... ) とすれば同じである。この場合は Array#map() の第 2 引数は不要である。

従って、アロー関数を使えば、この部分を [this].concat(args).map( ... ) とすることができる。さらに、新しめ版では Firefox 16 で導入された、配列リテラルの展開演算子を使っている。この演算子[1, 2, ...array, 3] のように書くと、array 部分が展開された、[1, 2, な, に, か, 3] のような配列ができるというものである。今回の例では [this].concat(args).map( ... ) とできるところで、[this, ...args].map( ... ) としている。なお、関数呼び出しにおける展開は Firefox 27 にて導入されるらしい。

今回使用した、rest パラメータ、アロー関数、展開演算子ECMAScript 6 の先行実装である。

追記。式クロージャFirefox 60 で死んでしまったようなので。 Array.slice なんかも死んでる模様。

Array.prototype.zip = function Array_zip(...args){
    return this.map((e, i) => [this, ...args].map(a => a[i]));
}