6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年1月1日 JavaScript (JS Primer) メソッドとコールバック関数とArrow Function

コールバック関数はcallback()のようにただの関数として呼び出されます。
つまり、コールバック関数として呼び出すとき、この関数にはベースオブジェクトはありません。 そのためcallback関数のthisはundefinedとなります。

// `callback`関数を受け取り呼び出す関数
const callCallback = (callback) => {
    // `callback`を呼び出す実装
};

const obj = {
    method() {
        callCallback(function() {
            // ここでの `this` は`callCallback`の実装に依存する
            // `callback()`のように単純に呼び出されるなら`this`は`undefined`になる
            // `Function#call`などを使って特定のオブジェクトを指定するかもしれない
            // この問題を回避するために`const that = this`のような一時変数を使う
        });
    }
};
  • Arrow Functionにおけるthisは呼び出し方の影響を受けません。 つまり、コールバック関数がどのように呼ばれるかという実装についてを考えることなくthisを扱えます。
const Prefixer = {
    prefix: "pre",
    prefixArray(strings) {
        return strings.map((str) => {
            // `Prefixer.prefixArray()` と呼び出されたとき
            // `this`は常に`Prefixer`を参照する
            return this.prefix + "-" + str;
        });
    }
};
const prefixedStrings = Prefixer.prefixArray(["a", "b", "c"]);
console.log(prefixedStrings); // => ["pre-a", "pre-b", "pre-c"]

メモ

  • JavaScriptでは、関数を定義するためにfunctionキーワードを使います。 functionからはじまる文は関数宣言と呼びます。
  • 関数宣言はfunctionから始まります。
  • 関数名に()をつけることで、関数としてまとめた処理を呼び出します。
  • 関数はオブジェクトのため、変数に代入できます。その場合も()を付けることで呼び出すことができます。
  • 関数が値として扱える(変数に代入したりできる)ことをファーストクラス(ファンクション)と呼びます。
  • ArrowFunctionは無名関数にのみ使用できます。
  • コールバック関数にはベースオブジェクトがありません。
  • コールバック関数にthis を定義する場合はArrow Functionを使用しなければなりません。

Arrow Functionはthisをbindできない

Arrow Functionで定義した関数ではthisを持つことが出来ないためcallapplybindを使ったthisの指定は単に無視されます。
これまで学んだようにArrowFunctionにthisが渡す暗黙的な値はありません。そのArrowFunctionに対して call apply bindthis の値を指定する事はできないのは当然ですね。

const fn = () => {
    return this;
};
// Scriptコンテキストの場合、スクリプト直下のArrow Functionの`this`はグローバルオブジェクト
console.log(fn()); // グローバルオブジェクト
// callで`this`を`{}`にしようとしても、`this`は変わらない
console.log(fn.call({})); // グローバルオブジェクト
  • Arrow Functionのthisが参照する「自身の外側のスコープにあるもっとも近い関数のthisの値」はcallメソッドで変更できます。
const obj = {
    method() {
        const arrowFunction = () => {
            return this;
        };
        return arrowFunction();
    }
};
// 通常の`this`は`obj.method`の`this`と同じ
console.log(obj.method()); // => obj
// `obj.method`の`this`を変更すれば、Arrow Functionの`this`も変更される
console.log(obj.method.call("THAT")); // => "THAT"

ここではArrowFunctionに対してcall を使っていないところがポイントです。obj.methodに対してcallを使っているのでthis の値を指定する事ができています。

まとめ

  • thisは状況によって異なる値を参照する性質を持ったキーワードです。

  • 実行コンテキストやstrict modeなどによって結果が異なり、混乱の元となります。 そのため、メソッドではない通常の関数においてはthisを使うべきではありません。

  • メソッドの中でコールバック関数を使う際は、静的にthisの値が決まるようにArrowFunctionで書きましょう。

  • this が指し示すのはベースオブジェクトである事は揺るぎないです。

ES2015の仕様編集者であるAllen Wirfs-Brock‏氏もただの関数においてはthisを使うべきではないと述べている。
https://twitter.com/awbjs/status/938272440085446657

参考

JS primer メソッドとコールバック関数とArrow Function

f:id:morning_6:20220101090504p:plain