👮‍♂️

【JavaScript】thisの束縛を解説

2023/07/16に公開

thisの束縛とは

JavaScriptのthisは実行コンテキストや関数の実行のされ方によって参照出来る値が異なります。このthisの参照先を開発者が指定した場所に固定することをthisの束縛と呼びます。

thisの参照先を固定するには、bind、call、applyという3つのメソッドを使用します。

thisについて詳しく知りたい方は下記の記事を参考にしてみてください。

https://zenn.dev/ryu0947/articles/1d7cd47407a186

bindメソッド

bindメソッドはthisの参照先を指定できます。thisを束縛したい関数にオブジェクトを指定すると、thisの参照先が束縛された新しい関数が作成されます。

bindメソッドは以下の引数を取ります。

Function.bind(obj, arg1, arg2);

obj: thisの参照先として指定したいオブジェクト 省略可
arg1, arg2: 新しく作成される関数に渡したい引数 省略可
戻り値: thisの参照先と引数を指定した新しい関数

基本的なbindメソッドの使い方として、まず第1引数にthisの参照先にしたいオブジェクトを指定します。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

const fn = person.greeting.bind(person); // thisの参照先にpersonオブジェクトを指定
fn(); // こんにちは太郎

上記のコードでは、personオブジェクトのgreetingメソッドに対してbindメソッドでthisの参照先を指定しています。変数fnにはbindメソッドでthisの参照先を指定した新しい関数が代入されています。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

const fn = person.greeting.bind(person); // thisの参照先にpersonオブジェクトを指定

/*
bindメソッドでこのような関数が新しく作成される
function fn() {
	console.log("こんにちは" + person.name);
}
*/

fn(); // こんにちは太郎

オブジェクトのメソッドが変数に代入されたりコールバック関数として引数に渡されるとメソッドではなく関数として実行されるため、参照先がグローバルオブジェクトになってしまいますが、bindメソッドを使用することによって意図した場所のthisに変更できます。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

function fn(cb) {
  cb();
}

fn(person.greeting.bind(person)); // 関数fnにコールバック関数としてthisを束縛した関数を渡す

bindメソッドは、thisの参照先以外にも引数を指定する事も出来ます。この引数は新しく作成する関数へ渡っていきます。

const person = {
  name: "太郎",
};

function greetingName(greeting) {
  console.log(greeting + this.name);
}

const helloTaro = greetingName.bind(person, "こんにちは"); // greetingName関数に引数として「こんにちは」を指定
helloTaro(); // こんにちは太郎

このbindメソッドに指定した引数は関数実行時に指定する引数よりも優先されます

const person = {
  name: "太郎",
};

function greetingName(greeting) {
  console.log(greeting + this.name);
}

const helloTaro = greetingName.bind(person, "こんにちは");
helloTaro("おはよう"); // この引数よりもbindで指定した引数が優先される。

bindメソッドはどの実行コンテキストのthisでもbindメソッドで指定したオブジェクや引数の方が優先されるためbindによるthisの束縛と表現されます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

callメソッド

bindメソッドは引数でthisの参照先を指定した新しい関数を作成しますが、実行はしません。callメソッドはthisの参照先を束縛した新しい関数の作成と実行を行います。

callメソッドは以下の引数を取ります。

Function.bind(obj, arg1, arg2);

obj: thisの参照先として指定したいオブジェクト 省略可
arg1, arg2: 新しく作成される関数へ渡したい引数 省略可
戻り値: thisの参照先と引数を指定した新しい関数の実行結果

callメソッドもbindメソッドと同じように第1引数にthisの参照先にしたいオブジェクトを指定します。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

person.greeting.call(person); // thisの参照先にpersonオブジェクトを指定

上記コードではpersonオブジェクトをthisの参照先として指定し、新しい関数の作成と実行を行っています。コードを実行してみると出力結果に「こんにちは太郎」が表示されているのが確認できるかと思います。

callメソッドの引数に何も指定しないとグローバルオブジェクトがthisの参照先として設定されます。グローバルオブジェクトはJavaScriptの組み込み関数やオブジェクトを持っている特殊なオブジェクトです。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

person.greeting.call(); // 引数に何も指定しないとグローバルオブジェクトがthisの参照先になる。

bindメソッドとcallメソッドの違いは、新しい関数を作成して実行するかしないかです。それ以外の点については同じなため、すぐに関数を実行したい場合はcallメソッドを使用し、何かしら操作を加えたい場合はbindメソッドを使用するという使い分けが基本となります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/call

applyメソッド

applyメソッドは、callメソッドと同じくthisの参照先の指定と関数の実行を行います。callメソッドとの違いは、第2引数の指定方法が配列という点です。

Function.bind(obj, [arg1, arg2]);

obj: thisの参照先として指定したいオブジェクト 省略可
[arg1, arg2]: 新しく作成される関数へ渡したい引数の値の配列 省略可
戻り値: thisの参照先と引数を指定した新しい関数の実行結果

applyメソッドの第1引数は、bindメソッドやcallメソッドと同じようにthisの参照先として指定したいオブジェクトですが、第2引数は配列を使用します。

window.name = "次郎";

const person = {
  name: "太郎",
  greeting: function (greeting, name) {
    console.log("こんにちは" + this.name);
    console.log(`${greeting}${name}`);
  },
};

person.greeting.apply(person, ["こんにちは", "太郎"]); // thisの参照先にpersonオブジェクトを指定

上記コードでは、applyメソッドの第2引数に指定した配列でgreetingメソッドの引数greetingとnameの値を指定しています。引数に指定した配列の値は分割されて関数の引数へ渡っていきます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

applyメソッドは引数に配列を取ることからthisの束縛以外に使用されるケースがあります。

applyメソッドの便利な使い方

applyメソッドの使い方としてよく紹介されるのがMath.max メソッドとMath.min メソッドで配列の最大値と最小値を取得する方法です。

const ary = [10, 20, 30];

// 引数に指定した配列の中で一番大きい値を返す
Math.max.apply(null, ary); // 30

// 引数に指定した配列の中で一番小さい値を返す
Math.min.apply(null, ary); // 10

Math.max メソッドとMath.min メソッドは引数に渡された値を比較して最大値・最小値を戻り値として返します。

applyメソッドを使わずに配列の値を比較したい場合はそれぞれ配列のインデックス番号で指定する必要があり、この書き方だと比較したい値が増えた時にその都度追記が必要になってしまいます。

const ary = [10, 20, 30];

// 配列の値をそれぞれ指定する必要があるため汎用性に欠ける
Math.max(ary[0], ary[1], ary[2]); // 30

// 配列の値をそれぞれ指定する必要があるため汎用性に欠ける
Math.min(ary[0], ary[1], ary[2]); // 10

applyメソッドを利用することで配列の要素数に左右されずに、別の関数やメソッドの引数に配列を渡すということが実現できます。

ただES2015に追加されたスプレッド構文でも同じことが出来ることと、こちらの方が簡潔に書く事が出来るため、単純に配列を引数として渡す場合はこちらの方が利用されることが多いようです。

const ary = [10, 20, 30];

// スプレッド構文で配列を展開する
Math.max(...ary); // 30

// スプレッド構文で配列を展開する
Math.min(...ary); // 10

あくまでapplyメソッドは、thisの参照先の束縛を併せて行いたい場合のみに使われます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

thisの束縛とグローバルオブジェクト

call、applyメソッドは第1引数にnullやundefinedを渡した場合、thisの参照先がグローバルオブジェクトになります。

function fn() {
    console.log(this);
}

fn.call(null); // グローバルオブジェクト ブラウザ環境ではWindwオブジェクト
fn.apply(null); // グローバルオブジェクト ブラウザ環境ではWindwオブジェクト
fn.call(undefined); // グローバルオブジェクト ブラウザ環境ではWindwオブジェクト
fn.apply(undefined); // グローバルオブジェクト ブラウザ環境ではWindwオブジェクト

ただこの挙動はStrictモードを有効にすると結果が変わります。

"use strict";

function fn() {
    console.log(this);
}

fn.call(null); // グローバルオブジェクトではなく、値がそのまま渡る
fn.apply(null); // グローバルオブジェクトではなく、値がそのまま渡る
fn.call(undefined); // グローバルオブジェクトではなく、値がそのまま渡る
fn.apply(undefined); // グローバルオブジェクトではなく、値がそのまま渡る

意図してグローバルオブジェクトを参照する機会は少ないと思いますが、Strictモードの有無で結果が変わるということは頭の片隅に置いておくといいかもしれません。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode

アロー関数とthisの束縛

アロー関数はthisを持ちません。そのため、bind、call、applyメソッドの第1引数を指定しても無効となります。

const obj = {
  prop: 0,
};

const fn = () => {
  console.log(this);
};

fn.call(obj); // アロー関数はthisを持たないため第1引数にthisの参照先を指定しても無効となります。
fn.apply(obj); // アロー関数はthisを持たないため第1引数にthisの参照先を指定しても無効となります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions

まとめ

thisの参照先を束縛するbind、call、applyメソッドについて解説しました。頻繁に使用するメソッドでは無いものの、覚えておいて損はないのでその際にこの記事が参考になれば嬉しいです。

最後までお読み頂きありがとうございました。

Discussion