✨

[JavaScript] thisをバインドするとなにが嬉しいのか

に公開

なにが嬉しいのか

bind関数を使うとthisをバインドできまっせ、という話は検索するといろいろヒットするが、では、thisをバインドするとなにが嬉しいのか? と一歩突っ込んで調べたので、勉強メモを残しておきます。

そもそもthisをバインドするとは

let user = { name: 'Alice' };

function greet(message) {
    console.log(`${message}, ${this.name}!`);
}

const greetAlice = greet.bind(user, 'Hello');

greetAlice();  // Hello, Alice!

通常thisは実行コンテキスト(関数がどこで呼ばれたか)で決まるが、bindはthisの参照先を開発者が指定した値に固定する。そして、bindは元の関数を実行せず、thisと引数が束縛された新しい関数を返す。この関数を後で実行することができる。また、thisだけでなく、関数の引数もbindの第二引数以降で指定でき、部分関数(引数の一部が固定された関数)を作成できる。

上の例では、関数greetをuserオブジェクトのthisにバインドし、引数'Hello'も固定した新しい関数を作成している。

細かい話は参考ページ[1]にかなり詳細に記されているので参照してください。

で、なにが嬉しいの?

thisをバインドする主なメリットは、関数が呼び出されるコンテキスト=実行環境を固定できることである。

コールバック関数でのコンテキストの維持

class Button {
    constructor(value) {
        this.value = value;
    }
    click() {
        console.log(this.value);
    }
}

const myButton = new Button('OK');
document.getElementById('myButton').addEventListener('click', myButton.click.bind(myButton));

この例では、イベントリスナーでメソッドを直接コールバックとして使用する際に、bindでthisをあらかじめ固定している。これにより、元のオブジェクトとの接続を失って関数内部のthisが予期しない値(グローバルオブジェクトやundefinedなど)になってしまうことを避け、コールバック関数内でもオブジェクトのプロパティやメソッドに正しくアクセスできる。

※オブジェクトがプロパティとして持っている関数=メソッドと呼んでいる。

特定のオブジェクトへの部分適用

bindの最初の引数でthisの値を設定し、それ以降の引数で関数の引数を部分的に適用(プリセット)できる。これにより、引数の一部が固定された新しい関数を作成できる。

これは部分適用 Partial Application と呼ばれるテクニックで、汎用的な関数からより具体的な関数を作るのに便利である。

function multiply(a, b) {
    return a * b;
}

const double = multiply.bind(null, 2);

console.log(double(3));  // 6 内部的には multiply(2, 3)
console.log(double(5));  // 10 内部的には multiply(2, 5)

これは2つの数値の掛け算という汎用的な関数から、ある数値の2倍の数値を返すというより具体的な関数を作っている。

アロー関数との関係

ES6(ECMAScript 2015)以降、アロー関数が導入された。アロー関数は関数が定義された時点のthisを静的に保持するので[2]、bindと似た効果を持っているので、開発現場ではbindの代わりに使うことも多い(らしい)。

つまり、通常の関数ではthisの値は実行時に動的に決まるが、アロー関数では関数が定義された時点で静的に決まる。これは、アロー関数が自身の独自のthisを持たないためである。

const obj = {
    name: 'オブジェクトA',
    traditionalFunction: function() {
        console.log('従来の関数:', this.name);

        const innerFunction = function() {
            console.log('内部関数:', this.name);
        };

        innerFunction();
    }
};

obj.traditionalFunction();
// 出力
// 従来の関数: オブジェクトA
// 内部関数: undefined(またはグローバルオブジェクトのname)

アロー関数の場合、

const obj = {
    name: 'オブジェクトB',
    arrowFunction() => {
        console.log('外側のアロー関数:', this.name);
    },

    methodWithArrow: function() {
        console.log('メソッド内のthis:', this.name);

        const innerArrowFunction = () => {
            console.log('内部アロー関数:', this.name);
        };

        innerArrowFunction();
    }
};

obj.arrowFunction();
obj.methodWithArrow();
// 出力
// 外側のアロー関数: undefined(など、グローバルを参照した値)
// メソッド内のthis: オブジェクトB
// 内部アロー関数: オブジェクトB

アロー関数は自身のスコープではなく、その関数を囲むもっとも近い非アロー関数のスコープ(レキシカルスコープ)からthisを継承する。上の例の場合、メソッド内のthisはobjを指し、内部アロー関数のthisは、囲んでいる非アロー関数methodWithArrowのthisを継承するので、objを指す。つまり、アロー関数内部のthisは定義された場所のスコープにあるthisを静的に(レキシカルに)継承するので、イベントハンドラやコールバック関数のなかで親オブジェクトのthisを参照したい場合には、bindでバインドしなくてもよい。

まとめ

thisをバインドすると、「この関数は常にこのオブジェクトのコンテキストで実行してほしい」という意図を明確にすることができる。あえてアロー関数を使わずにbindを用いて意図的にthisをバインドするということは、開発者からのコードの意図についてのメッセージとして機能するといえる。

  1. thisの参照先の固定化と保証
  2. コードの意図を明確にする
  3. コンポーネント志向プログラミングでの規範

補足すると、Reactなどのフレームワークでは、クラスコンポーネントのメソッドをイベントハンドラとして使う場合、thisをコンポーネントインスタンスにバインドすることが標準的である(アロー関数で代替することもある)。これも、フレームワークの仕組みの中でthisが意図したものを指すようにするための明確な意図に基づいている。

参考ページ

[1] https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
[2] https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Discussion