お知らせ:2021/2/18 ツールサイト(affi-sapo-sv.com)から、開発ノートを独立させました。
【JavaScript】 今更だがbind()について理解してみる
更新日:2021/02/05
ツイートJavaScriptにはbindというメソッドがあって、コールバック関数内でthisを使用するときなどよく使っています。
ですが僕の場合、サンプルで見たコードで使っていたから、マネして使っていただけでした。
「これではいかん!」
ということで、今更ですがbindについて調べてみました。
bind()とは
bindは関数オブジェクトが持つプロトタイプメソッドです。
bindを実行すると、『Bound Function Exotic Objects』が作成されリターンされます。
日本語にすると…『バインドされた関数のエキゾチックなオブジェクト』…わかりませんね!
エキゾチックなオブジェクトは普通でないオブジェクトという意味ですが、プログラム上はあまり気にする必要はありません。
でも一応記事にしてあります…
参考:【JavaScript】 エキゾチックなオブジェクトってなに?
Bound Function Exotic Objects
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を持っていない
BFオブジェクトは、内部の関数オブジェクトをthisや引数を指定して実行します。
では関数オブジェクト単体で実行されたとき、thisはどこから取得されるのでしょうか?
関数オブジェクトはthisを持っていません。
そのため外部からthisを指定してもらわないと、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()を呼び出しています。
そのため、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を使用するケースが減っています。
今後使う機会があるかどうかわかりません…
記事の内容について

説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
ご意見はこちら。
https://note.affi-sapo-sv.com/info.php
【お願い】

■このページのURL
■このページのタイトル
■リンクタグ