this関数・メソッド

【JavaScript】 今更だがbind()について理解してみる

更新日:2021/02/05

JavaScriptにはbindというメソッドがあって、コールバック関数内でthisを使用するときなどよく使っています。

ですが僕の場合、サンプルで見たコードで使っていたから、マネして使っていただけでした。

「これではいかん!」

ということで、今更ですがbindについて調べてみました。

 

bind()とは

bindは、関数にthis値や引数を結びつけることができます。

通常オブジェクトのメソッドは、呼び出されたときにオブジェクト自信をthis値として受け取ります。


const obj = {
    prop1:100,
    func:function (){ console.log(this);}
}
obj.func();
// { prop1: 100, func: [Function: func] }

しかし、メソッドを変数に代入してから実行すると、オブジェクト自信をthis値として受け取ることができません。


const objFunc = obj.func;
objFunc();
// undefined または、グローバルオブジェクト(windowやglobal)

理由については、次のページを読んでみてください。
【JavaScript】 関数(function)とメソッドの違い

そこで、変数代入時にbindを実行すると、this値を紐づけることができます。


const objFunc = obj.func.bind(obj);
objFunc();
// { prop1: 100, func: [Function: func] }

Bound Function Exotic Objects

bindは関数オブジェクトが持つプロトタイプメソッドです。

bindを実行すると、『Bound Function Exotic Objects』が作成されリターンされます。

日本語にすると…『バインドされた関数のエキゾチックなオブジェクト』…わかりませんね!

エキゾチックなオブジェクトは普通でないオブジェクトという意味ですが、プログラム上はあまり気にする必要はありません。
でも一応記事にしてあります…
参考:【JavaScript】 エキゾチックなオブジェクトってなに?

Bound Function Exotic Objects…長いのでBFオブジェクトは、内部的に次の3つのオブジェクトを持っています。

BFオブジェクトの内部オブジェクト

■ [[BoundTargetFunction]]

元となった関数オブジェクト

■ [[BoundThis]]

this値

■ [[BoundArguments]]

引数のリスト

BFオブジェクトの動作

BFオブジェクトを実行すると、[[BoundTargetFunction]]が実行されます。

そのとき、this値([[BoundThis]])や引数([[BoundArguments]])が使用されます。

仕組みとしては、とても簡単ですね。

BFオブジェクトに引数をつけて実行

次の例のように関数から、引数を一つ指定したBFオブジェクトを作成します。


const a = function(){};
const bf = a.bind( null , 1 );
// ※関数でthisを使用しない場合nullでOK

BFオブジェクトに引数をつけて実行します。


bf( 2, 3, 4); // Arguments { 0: 1, 1: 2, 2: 3, 3: 4, … }

元の関数aには、bind()で指定した引数の後に、BFオブジェクト実行時の引数が渡されます。

引数(
     1 , // bindで指定
     2 , // 引数で指定
     3 , // 引数で指定
     4 , // 引数で指定
)

BFオブジェクト.call()の動作

BFオブジェクトに対してcallメソッドを実行した場合、どうなるのでしょうか。

次の例で確認してみます。


const a = function(){
            console.log( this);
            console.log( arguments);
};
const bf = a.bind( { x : 10 } , 1 );

上の例では、thisとして オブジェクト{ x : 10 }を、引数として1をセットしたBFオブジェクトを作成しています。

次に、BFオブジェクトをcallメソッドで実行してみます。


bf.call( { y : 20 } , 2, 3, 4);
    // Object { x: 10 }
    // Arguments { 0: 1, 1: 2, 2: 3, 3: 4, … }

上の例では、callにthisとして オブジェクト{ y : 20 }を渡しました。

しかし、関数コード内で表示されたthisは、bindでセットしたオブジェクト{ x : 10 }です。

つまりcallのthisは無視されたのです。

実際に仕様上でも、thisArgumentが使用されずに放置されています。かわいそうです。

引数に関しては普通に実行したときと同じように、bindで指定した引数の後に追加されます。

備考:関数オブジェクトはthisを持っていない

関数オブジェクトは外部からthisや引数を指定してもらわないと、それらを扱うことができません。

BFオブジェクトは、内部で記憶している関数オブジェクトを実行するときに、thisや引数を指定します。

関数オブジェクトが直接実行されたときは、thisはどこから指定されるのでしょうか?

詳しくはこちらをご覧ください。
参考:【JavaScript】 thisは本当に単純なものだった

 

bindとレキシカル環境(クロージャ)

JavaScriptはレキシカルスコープで動作しています。
その特性上、次のような例では外部の変数(レキシカル環境)を保持します。

次の例のように、関数内で作成された関数は、外側の変数を記憶しています。


function a(){
            let param = 5; // (1)
            return function () {
                console.log( param );
            }
}

function c() {
            let param = 10;
            const d = a( );  
            d(); // 結果:5 ← dは(1)を記憶している
}
c( );

ちなみに関数内で作成した関数のことをクロージャということがあります。
参考:【JavaScript】クロージャとは?要点をまとめてみる
参考:【JavaScript】 必須知識?スコープとレキシカル環境

ではbindした場合は、どうなるのでしょうか。


function a(){
            let param = 5;
            return function () {
                console.log( param );
            }
}

function c() {
            let param = 10;
            const d = a( ).bind(null);
            d();  // 結果5
}
c( );

結果は同じです。

bindで生成されたBFオブジェクトは、普通の関数オブジェクトと同じように使用できます。

 

bindとコールバック関数

bindは、コールバック関数にthisを伝える目的で使用されることが多いです。

では実際に、どのように作用しているのでしょうか。

コールバック関数のthisはグローバルオブジェクト


function callBackTest(callBack) {  // コールバックを受け取る関数
            callBack();  // コールバック関数を実行
}

function a(){
            this.param = 5;
};

a.prototype.func = function () {

            callBackTest(
                   function () { // コールバック関数定義
                           console.log( this ); // window (strictモードはundefined)
                           console.log( this.param ); // undefined
                  }

            );
};

const b = new a;
b.func();

上の例は、a.prototype.func内で、コールバック関数を引数で受け取るcallBackTest()を呼び出しています。

関数オブジェクトはthisを持っていません。

そのため、callBackTest内で実行されたコールバック関数のthisは、初期値のグローバルオブジェクト(ブラウザはwindow)になります。(strictモードはundefined)

その結果、想定通りの動作を得ることができません。

bindでコールバック関数にthisをセット

そこで活躍するのがbindです。

次のように、関数定義(式)にbindでthisを指定します。


a.prototype.func = function () {
            callBackTest(
                   function () { // コールバック関数定義
                           console.log( this ); // Object { param: 5 }
                           console.log( this.param ); // 5
                  }.bind ( this )
            );
};

こうすることでコールバック関数の受け取り側は、thisがセットされたBFオブジェクトを実行することになります。


function callBackTest(callBack) {
            callBack();  // thisに{ param: 5 }がセットされたBFオブジェクトを実行
}

ここで勘違いしてはいけないのが、a.prototype.func内のthisと、コールバック関数内のthisは何の関係もないことです。
同一のオブジェクトを参照しているという意味では関係がありますが、処理の流れ的に関係がないということです。

例えば、次のように好きなオブジェクトをthisとして渡すことができます。


function () { // コールバック関数定義
                           console.log( this ); // Object { x: "abced" , y: 100 }
                           console.log( this.param ); // undefined
                  }.bind( { x : "abced" , y : 100 } );

つまり、たまたまセットしたのが実行中のthisだったということですね。

なお、thisをそのままコールバック関数で使用したいときは、bindを使用せずに、アロー関数を使用したほうがわかりやすいです。


a.prototype.func = function () {
            callBackTest(
                    () => { // コールバック関数定義(アロー関数)
                           console.log( this ); // Object { param: 5 }
                           console.log( this.param ); // 5
                  }
            );
};

アロー関数については、こちらを見てください。
参考:【JavaScript】 アロー関数は何者!?かっこいいだけじゃない!

DOM関数のthisとbind

イベントを処理するDOM関数は、イベントが発生した要素をコールバック関数にthisとして渡します。

JavaScript


window.addEventListener("DOMContentLoaded",()=> {
    document.getElementById("btn").addEventListener( "click",
        function () {
            console.log(this); // <p id="btn">をthisとして受け取った
        }
    );
});

HTML


<p id="btn">click!</p>

自動で渡されてくるイメージを持っている人が多いですが、次のようにaddEventListener内でthisをセットしています。


function addEventListener( type , callBack ){
        callBack.call( イベントが発生した要素 , その他のパラメータ );
}

上の例は、実際の処理とは異なります。
関数内でthisをセットしているイメージを持ってもらえればOKです。

ではコールバック関数にbindでthisをセットしてみます。

JavaScript


window.addEventListener("DOMContentLoaded",()=> {
    document.getElementById("btn").addEventListener( "click",
        function () {
            console.log(this); // { x : 100 }をthisとして受け取った
        }.bind( { x : 100 } )
    );
});

BFオブジェクト.call()の動作で説明したように、BFオブジェクトのcallメソッドは、新しいthisを受け付けません。

そのためaddEventListener内で、thisをセットしようとしても無視されてしまうのです。

 

まとめ

今回はbind()について調べてみました。

僕的には、コールバック関数内でthisを使用するために、bindを多用していました。
ですがアロー関数の登場でbindを使用するケースが減っています。

今後使う機会があるかどうかわかりません…

更新日:2021/02/05

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。