【初心者向け】JavaScriptのbindって何??を理解する(call, appyも一緒に)
この文書を読んでいるということはJavaScriptの初心者の方でbindメソッドがどのようなものか理解しようとしている人、bindとthisとの関係がわからないという人またはthis自体の理解が曖昧な人ではないでしょうか。特にthisの理解が曖昧な人は多いと思うのでこの機会にしっかりと学んでおきましょう。
bindを学ぶ前にbindとは何かまずMDN Web Docsに記述されている内容を確認してみましょう。
bindメソッドが理解できていない時は上記の説明を読んでも何を言っているのかわからないと思います。今意味がわからなくてもbindはthisと関係していること、bindを利用することである関数から新しい関数を作成することができることの2つを頭に入れて下記の文書を読み進めていけば最終的には上記の意味が掴めるかと思います。bind、thisを説明する際に抽象的でわかりにくい単語が使われている場合がよくあります本文書ではプログラミングの概念を説明する際に用いられる難解な言葉をできるだけ避けているので安心して読み進められるかと思います。
bindが理解できればcall関数もapply関数も一緒に理解することができるのでcall, applyについても触れています。本文書で3つの関数とthisの理解を深めることができます。
目次
thisの中身を確認する
例を使いながらbindの利用方法を確認していきますが、bindはthisと関係があるので最初はthisに注目しながら動作確認を行っていきます。基本はnode環境でJavaScriptのコードを実行していますが一部わかりやすいようにChromeのデベロッパーツールのコンソールも利用しています。
プロパティcontentとclickを持つmyButtonを定義し、clickメソッドを実行してthisにはどのようなものが入っているのか確認を行います。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked');
}
};
myButton.click();
実行すると下記が実行後に表示されるメッセージでthisにはmyButtonのオブジェクトが入っていることが確認できます。上記のコードではclickメソッド内でthisの中に実行しているオブジェクト(myButton)が参照できていることがわかります。thisが参照しているオブジェクトはclick関数の”.”(ドット)の左側にあるmyButtonであることもわかります。
{ content: 'OK', click: [Function: click] }
OK clicked
さらにyourButtonを追加してyourButtonでthisを使い下記を実行してみましょう。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked')
},
yourButton: {
content: 'No',
click() {
console.log(this)
console.log(this.content + ' clicked your button')
}
}
}
myButton.click();
myButton.yourButton.click();
実行すると先ほどの例と同様にmyButton.click()では.(ドット)の左側のmyButtonがthisの参照するオブジェクトになっています。myButton.yourButton.click()ではclickの左側のyourButtonが参照するオブジェクトになっていることが確認できます。
{
content: 'OK',
click: [Function: click],
yourButton: { content: 'No', click: [Function: click] }
}
OK clicked
{ content: 'No', click: [Function: click] }
No clicked your button
次にmyButtonの内容は最初のものに戻しmyButton.clickを使ってlooseClickを定義し実行します。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked');
}
};
const looseClick = myButton.click;
looseClick();
先ほどの処理と同じ結果が出るのではと思ってしまいますがlooseClickを実行した場合は、thisがmyButtonではなくグローバルオブジェクト(ブラウザではWindowオブジェクト)を参照しているため結果はthis.contentがundefinedになってしまいます。
Object [global]
undefine clicked
Button.click()とlooseClick()の実行はコードの流れから同じものだと考えてしまいますがJavaScriptでは異なることに注意する必要があります。
ここまでに行った動作確認からthisは実行されるオブジェクトの内容によって決まるのではなく実行する方法によって決まることがわかります。つまりmyButtonの中身でthisの参照が決まっているわけではありません。
bindの利用方法(1)
thisがどこを参照することになるのかがこれまでの例から少しだけ理解が進んだと思うのでここからbindを使って説明を行います。
先ほど確認を行ったlooseClickの実行でthisが参照するものを明示的に指定することができれば期待した結果を表示することができます。
bindを利用することでどのthisを参照するのか指定することができます。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked');
}
};
const boundClick = myButton.click.bind(myButton);
boundClick();
bindの引数にmyButtonオブジェクトを入れることでthisがmyButtonを参照するように設定を行うことができます。thisにMyButtonが入ったため結果は一番最初のコードと同じものになります。
{ content: 'OK', click: [Function: click] }
OK clicked
ここまでの説明でbindがthisに関係していることさらにbindによりthisに指定したオブジェクトを設定できることが理解できたかと思います。
コードを眺めているとmyButton.clickにはmyButtonと同じオブジェクトをbindで利用しているためbindでは同じオブジェクトを指定しないといけないのではという疑問が出てきます。実際に異なるオブジェクトをbindで指定してどうなるか確認してみましょう。
下記のようにyourButtonを新たに定義しcontentプロパティを追加します。bindには新たに定義したyourButtonを指定します。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked');
}
};
const yourButton = {
content: 'No'
}
const boundClick = myButton.click.bind(yourButton);
boundClick();
実行するとmyButtonのオブジェクトだけではなく他のオブジェクトでもbindで利用できることが確認できます。
{ content: 'No' }
No clicked
bindの利用方法(2)
ここまでの例だけでかなりbindの理解は進んだと思いますが別の例を使ってさらにbindの設定方法を確認します。
greetingを定義し、bind前と後でgreeting関数を実行しています。bindを実行すると新しい関数を作成することができるのでbind後ではbindで作成した新しい関数を実行しています。greeting関数を実行した際にthisは何も指定していないのでthisに何が入るのか確認してみましょう。
const greeting = function(){
console.log(this)
console.log(this.name);
}
greeting()
const object = {name:"John"}
const new_greeting = greeting.bind(object)
new_greeting()
bindを行っていない場合はthisがグローバルオブジェクトを参照することになり、this.nameはundefinedとなります。そのあとにbindにobjectを設定することでthisの参照先をグローバルオブジェクトからbindで指定したobjectに変更することができます。その結果thisがobjectを参照しているためthis.nameを表示することが可能になります。
//bind前
Object [global]
// undefined
//bind後
{ name: 'John' }
John
bindを利用して新しい関数new_greetingを作成しているので冒頭で説明したMDN Web Docsに記述されている”bindメソッドは、呼び出された際にthisキーワードに指定された値が設定される新しい関数を生成します。”という説明もはっきりとは言わないまでもなんとなく理解できるかようになったかと思います。
アロー関数を利用した場合のbind
ここまでは関数に標準関数を利用していましたがここではgreetingにアロー関数を利用して先ほどと同じことを実行してみましょう。アロー関数は標準関数の短縮系というだけではなく他にも違いがあることをしっかり理解しましょう。
const greeting = () => {
console.log(this)
console.log(this.name);
}
greeting()
const object = {name:"John"}
const new_greeting = greeting.bind(object)
new_greeting()
アロー関数に変更しても結果は同じだと思っている人も多いかと思いますが、アロー関数ではbindを利用してもthisを変更することはできません。bind前後で結果は同じです。アロー関数ではthisはそれ自身を参照しているので空のオブジェクトが表示されます。
{}
undefined
{}
undefined
アロー関数ではthisがgreetingを参照しているのでgreetingの中でnameを追加します。
const greeting = () => {
this.name = 'Kevin'
console.log(this)
console.log(this.name);
}
greeting()
const object = {name:"John"}
const new_greeting = greeting.bind(object)
new_greeting()
thisがそれ自身を指しているのでbindするしないにかかわらずgreetingの内部で設定したnameのKevinが表示され同じ結果になります。
{ name: 'Kevin' }
Kevin
{ name: 'Kevin' }
Kevin
Fetch関数を使った例
ここまで読み進めた方であればbindメソッドがどのようなものかスッキリし始めているころかなと思います。さらに次はfetch関数を利用した場合のbindの例で説明を行いたいと思います。
ourUsersの中のgetUsersメソッドを利用して外部からユーザ情報を取得してusersの中に取得した配列を保存する処理を実行しています。下記の場合にthisには何が入るのか確認しています。
const ourUsers = {
users: [],
getUsers() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(function (response) {
return response.json();
})
.then(function (users) {
this.users = users;
console.log(this)
})
}
}
ourUsers.getUsers()
上記を実行すると下記の結果となります。この場合thisはourUsersではないのでグローバルオブジェクト(ブラウザの場合はWindowオブジェクト)のためグローバルオブジェクトのusersに取得したデータが保存されます。
表示されたWindowオブジェクトをさらに展開するとさまざまなプロパティに紛れてしっかりとusersを見つけることができます。
this(ourUsersオブジェクト)をbindで指定することでourUsersのusersに保存することができるようになります。
const ourUsers = {
users: [],
getUsers() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(function (response) {
return response.json();
})
.then(function (users) {
this.users = users;
console.log(this)
}.bind(this)) //ここでbindを行う
}
}
ourUsers.getUsers()
bindを利用せずアロー関数(thenの中のコードを変更)を利用することでもbindと同じ結果となります。またアロー関数だとコードもすっきりします。
const ourUsers = {
users: [],
getUsers() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {
this.users = users;
console.log(this)
})
}
}
ourUsers.getUsers()
asyn, await関数を利用した場合もthisはアロー関数を同じ結果になります。
const ourUsers = {
users: [],
async getUsers() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
this.users = await res.json();
console.log(this)
}
}
ourUsers.getUsers()
シンプルな例を3つ利用してbindを説明してきましたがここまでの説明でbindメソッドがどのようなものな完全に理解とは言えなくても理解は進んだのではないでしょうか。thisを利用した時に意図した通りの動作になっていない場合はここまでの例を参考にthisが何を参照しているか確認することでthisの参照先がわかればthisに関係する問題は解決できるかもしれません。
bind関数の引数の設定
bindには引数をとることができます。thisに設定するオブジェクト以外の引数を設定して動作確認を行ってみます。bindにまずthisに入るオブジェクトを設定します。greetingに設定した関数にはageとheightの2つの引数を取ることができます。bindを利用することでbindの2つ目、3つ目の引数には関数の引数に対応するageとheightの値を入れることができます。
const greeting = function(age, height){
console.log(`${this.name}の年齢は${age},身長は${height}です。`)
}
const object = {
name: 'John',
}
const new_greeting = greeting.bind(object,20,'175cm');
new_greeting()
bindの設定した2つ目、3つ目がage, heightに渡され下記の結果が表示されます。
Johnの年齢は20,身長は175cmです。
call関数とbindの違い
上記の説明でbindにはthisと引数を取ることができるということがわかったと思うのでその理解を元にするとcall関数の理解は簡単です。
call関数のは下記のように記述することができます。最初の引数にはthis,2つ目以降には引数をとることができます。どこかで見覚えがあるかと思いますがbindの形と同じです。
call(thisArg, arg1, arg2)
ではcallとbindでは何が異なるのでしょう。本文書のbindの利用方法(1)で説明したbindを例にするとbindを利用した場合は、下記のように新しい関数を作成することができ、その新しい関数を実行していました。
const myButton = {
content: 'OK',
click() {
console.log(this)
console.log(this.content + ' clicked');
}
};
const boundClick = myButton.click.bind(myButton);
boundClick();
boundClickに一度関数を保存せずそのまま実行することも可能です。実行するために後ろに()が付いていることに注意してください。
myButton.click.bind(myButton)();
callの場合は新しい関数を作成するのではなくそのまま関数を実行することができます。上のコードと比較したら違いに明確になるかと思います。確認後、実際に動作確認をしてみましょう。
const myButton = {
content: 'OK',
click() {
console.log(this);
console.log(this.content + ' clicked');
},
};
myButton.click.call(myButton);
結果はbindで実行した場合と同じです。
{ content: 'OK', click: [Function: click] }
OK clicked
もう一つ引数が入った時のbindの例をcallに変えて実行してみましょう。
const greeting = function(age, height){
console.log(`${this.name}の年齢は${age},身長は${height}です。`)
}
const object = {
name: 'John',
}
greeting.call(object,20,'175cm'); //call関数
//結果
Johnの年齢は20,身長は175cmです。
bind関数とcall関数の違いが理解できたかと思います。
bind関数を利用する場合は下記のコードなので比較してください。
const greeting = function(age, height){
console.log(`${this.name}の年齢は${age},身長は${height}です。`)
}
const object = {
name: 'John',
}
const new_greeting = greeting.bind(object,20,'175cm');
new_greeting()
apply関数の確認
call関数を確認したのでapply関数も確認しておきましょう。apply関数の場合は引数に配列を設定します。引数を使って例で確認してみましょう。
第2引数が配列になっていることを確認してください。違いはそれだけです。
const greeting = function (age, height) {
console.log(`${this.name}の年齢は${age},身長は${height}です。`);
};
const object = {
name: 'Jack',
};
greeting.apply(object, [18, '173cm']);
//結果
Jackの年齢は18,身長は173cmです。
では引数がない場合は?
const myButton = {
content: 'OK',
click() {
console.log(this);
console.log(this.content + ' clicked');
},
};
myButton.click.apply(myButton);
結果はcallと同じです。
add関数による確認
引数をa,bに取るadd関数を作成します。実行するためにadd(1,2)であることはほとんどの人がわかると思います。
function add(a,b){
console.log(a+b)
}
add(1,2)
bind, call, applyを利用してもadd関数を実行することができます。
function add(a,b){
console.log(a+b)
}
add(1,2)
add.bind(null, 1, 2)()
add.call(null, 1, 2)
add.apply(null, [1, 2])
非常にシンプルなコードを利用しているので実践的ではないかもしれませんが、本文書を通してbind関数, this, call関数, apply関数がどのようなものかわかってもらえたら大変うれしいです。