javascriptの配列操作

こんにちは、ニッパチです。
javascriptでよく使う配列の組み込み関数、mapやfilterとか便利ですがどんな処理をするものなのか
自分なりに考えてみました。

最初

Arrayクラスを拡張したMyArrayクラスを定義

class MyArray extends Array {
    // mapの中身を実装してみる
    my_map() {
    }

    // filterの中身を実装してみる
    my_filter() {
    }

    // reduceの中身を実装してみる
    my_reduce() {
    }
}

map

まずはmapから
ざっくりとはこんな感じになるかと

class MyArray extends Array {
    // mapの中身を実装してみる
    my_map(callback) {
        let new_ary = [];
        for (let i = 0; i < this.length; i++) {
            let new_item = callback(this[i], i);
            new_ary.push(new_item);
        }
        return new_ary;
    }

    // filterの中身を実装してみる
    my_filter() {
    }

    // reduceの中身を実装してみる
    my_reduce() {
    }
}

// 使用するコールバック関数
function double(item) {
    return item * 2;
}
function double2(item, index) {
    return item * index;
}

let array = new MyArray(1,2,3);

let r = array.my_map(double)
let r2 = array.my_map(double2)
console.log(r)
// [2,4,6]
console.log(r2)
// [0,2,6]

mapと同じ結果となりました。しかし、
こちらからmapの使用を調べると、map自体の引数は2つでコールバックの引数は3つでした。
なんか多いな。僕の普段使いで考えるとmapにはコールバックしか渡さないし、コールバックの引数も多くて2つまでしか使わないのでその他の引数はどういう意味なのか調べるとします。
まず、引数の詳細は以下の通りです。

  • callbackFn コールバック
    • element
    • index
    • array mapが呼び出された配列
  • thisArg(省略可) callbackFnを実行するときにthisとして使う値

thisArgはなんのことかさっぱりです。
ちょっと実験です。

let array = [1,2,3];
function test() {
    return this;
}
let r = array.map(test);
console.log(r);
// 結果
Windowオブジェクトが3つ入った配列

let obj = {name: 'regrex'}
r = array.map(test, obj);
console.log(r);
// 結果
objが3つ入った配列

ちょっとわかったかも。
この辺も考慮して改造したmy_mapがこちら

class MyArray extends Array {
    // mapの中身を実装してみる
    my_map(callback, that = null) {
        let new_ary = [];
        if (that) {
            callback = callback.bind(that)
        }
        for (let i = 0; i < this.length; i++) {
            let new_item = callback(this[i], i, this);
            new_ary.push(new_item);
        }
        return new_ary;
    }

    // filterの中身を実装してみる
    my_filter() {
    }

    // reduceの中身を実装してみる
    my_reduce() {
    }
}

filter

続いてfilter。ドキュメントをみると引数はmapとまったく同じでした。

class MyArray extends Array {
    // mapの中身を実装してみる
    my_map(callback, that = null) {
        // 省略
    }

    // filterの中身を実装してみる
    my_filter() {
        let new_ary = [];
        if (that) {
            callback = callback.bind(that)
        }
        for (let i = 0; i < this.length; i++) {
            if (callback(this[i], i, this)) {
                new_ary.push(this[i]);
            }
        }
        return new_ary;
    }

    // reduceの中身を実装してみる
    my_reduce() {
    }
}

let array = new MyArray(1,2,3);
let r = array.my_filter(x => x % 2);
console.log(r)
// [1,3]

奇数だけの配列が返ってきて期待通りの結果となりました。

reduce

最後にreduceです。こいつに関しては普段使っていても「あれ?これなんだっけ?」とすぐなってしまいます。
ドキュメントを確認すると、初期値が指定されていない時や配列の長さによって挙動を制御する必要があります。

class MyArray extends Array {
    // mapの中身を実装してみる
    my_map(callback, that = null) {
        // 省略
    }

    // filterの中身を実装してみる
    my_filter(callback, that = null) {
        // 省略
    }

    // reduceの中身を実装してみる
    my_reduce(callback, initialVal = null) {
        if (this.length === 0 && initialVal === null) {
            throw TypeError('エラー');
        }
    if (this.length === 1 && initialVal === null) {
            return this[0];
        }

        let previousVal = this[0];
        let currentVal = this[1];
        let currentIndex = 1;
        let result;

        if (initialVal) {
            previousVal = initialVal;
            currentVal = this[0];
            currentIndex = 0;
        }

        for (let i = currentIndex; i < this.length; i++) {
            result = callback(previousVal, currentVal, currentIndex, this);
            previousVal = result;
            currentIndex += 1;
            currentVal = this[currentIndex];
        }
        return result;
    }
}

function test(previous, current, index, arr) {
    return previous + current;
}

let array = new MyArray(1,2,3);
let r = array.my_filter(test);
console.log(r)

これで期待通りの結果となりました。