JavaScriptのbind()
メソッド解説
1. はじめに
下記のコードでなぜthis.name
の出力結果がundefinedになるか、あなたは説明できるでしょうか?
const person = {
name: "Alice",
greet: function () {
console.log("こんにちは、" + this.name + "です。");
}
};
const greetFunction = person.greet;
greetFunction(); // 出力: こんにちは、undefinedです。
説明できなかった方この記事を読んで損はないはずです
JavaScriptのbind()
メソッドは、関数の挙動を制御するためのメソッドですが、初学者が非同期処理と同じくらい理解不能に陥ってしまうのがこのbind()
です。
今回はbind()
の使い方と概要について解説していきます。
2. 関数の呼び出し方とthisの値
まず。大前提として理解しておかないといけないことは、JavaScriptでは関数の呼び出し方によってthis
の値が変わる というころです。
まずの次の見慣れた形を見ていきましょう。
2.1 オブジェクトのメソッドとして呼び出す
const person = {
name: "Alice",
greet: function() {
console.log("こんにちは、" + this.name + "です。");
}
};
person.greet(); // 出力: こんにちは、Aliceです。
この場合、this
はperson
オブジェクトを指します。よく見る通常のthisですね。
2.2 単独の関数として呼び出す場合
では冒頭に出てきたコードをもう一度確認してみましょう。
以下のように呼び出すとどうなるでしょうか??
const person = {
name: "Alice",
greet: function () {
console.log("こんにちは、" + this.name + "です。");
}
};
const greetFunction = person.greet;
greetFunction(); // 出力: こんにちは、undefinedです。
ご覧の通り出力結果がundefind
となってしまいます。
これは、関数(greet)がオブジェクトから切り離されて呼び出されているためthisはglobalオブジェクトを探しに行った結果、見当たらずundefindとなっています。
初心者用にさらに詳しく解説(理解できている人はスキップ可)
-
関数の切り離し
const greetFunction = person.greet;
この行で、
person.greet
関数をgreetFunction
変数に代入しています。この時点で、関数はperson
オブジェクトから「切り離された」状態になります。
-
切り離された関数の呼び出し:
greetFunction("Hello");
この呼び出しでは、関数は
person
オブジェクトのメソッドとしてではなく、独立した関数として実行されます。
-
this
の挙動- JavaScriptでは、関数が単独で呼び出された場合(つまり、オブジェクトのメソッドとしてではなく)、
this
はデフォルトでグローバルオブジェクトを参照します。 - ただし、strict modeではこのデフォルトの動作が変わり、
this
はundefined
になります。
- JavaScriptでは、関数が単独で呼び出された場合(つまり、オブジェクトのメソッドとしてではなく)、
-
undefined
の結果:- 関数内で
this.name
にアクセスしようとしても、this
がグローバルオブジェクト(またはstrict modeではundefined
)を指しているため、name
プロパティは存在しません。 - そのため、
this.name
はundefined
となり、結果として「Hello, I'm undefined」のような出力になります。
- 関数内で
そして、これを解決してくれるのがbind() です。
次の章で解説しますが先に簡単に予習しておきましょう。
例えば以下のように、切り離された関数に対してbind()を使用すると...
const boundGreet = person.greet.bind(person);
boundGreet("Hello"); // 正しく "Hello, I'm Alice" と出力されます
このように、bind()
によってグローバルオブジェクトを探しに行くthis
に対して「おいおい、そこまで探しに行かなくていいよ!この値をみればいいよ!」という風にperson
オブジェクトに固定(bind)してくれるというわけです。
3. bind()メソッドの役割
bind()
メソッドは、関数のthis
値を固定する新しい関数を作成します。
3.1 基本的な使い方
const boundGreet = person.greet.bind(person);
boundGreet(); // 出力: こんにちは、Aliceです。
bind(person)
は、グローバルオブジェクトを探しに行くthis
をperson
オブジェクトに固定した新しい関数を作ります。
3.2 引数の事前設定
bind()
はthis
の固定だけでなく、引数も予め設定できます。
function greet(greeting, name) {
console.log(greeting + ", " + name + "!");
}
const greetAlice = greet.bind(null, "こんにちは");
greetAlice("Alice"); // 出力: こんにちは, Alice!
4. bind()が役立つ場面
- コールバック関数で元のオブジェクトの参照を維持したい場合
- 一部の引数を固定した新しい関数を作成する場合
- イベントリスナーで特定のオブジェクトのメソッドを使用する場合
各項目を実際のコード例で見ていきましょう
4.1 コールバック関数で元のオブジェクトの参照を維持したい場合
const user = {
name: "Alice",
greet: function() {
console.log("こんにちは、" + this.name + "です。");
}
};
// bindを使用しない場合
setTimeout(user.greet, 1000);
// 1秒後に出力: こんにちは、undefinedです。
// bindを使用する場合
setTimeout(user.greet.bind(user), 1000);
// 1秒後に出力: こんにちは、Aliceです。
この例では、setTimeout
のコールバックとしてuser.greet
を渡す際にbind()
を使用することで、this
がuser
オブジェクトを正しく参照するようになります。
4.2 一部の引数を固定した新しい関数を作成する場合
function multiply(a, b) {
return a * b;
}
// 5を固定した新しい関数を作成
const multiplyByFive = multiply.bind(null, 5);
console.log(multiplyByFive(3)); // 出力: 15
console.log(multiplyByFive(7)); // 出力: 35
この例では、bind()
を使って最初の引数を5に固定した新しい関数を作成しています。null
はthis
の値を変更しないことを示しています。
4.3 イベントリスナーで特定のオブジェクトのメソッドを使用する場合
<button id="incrementButton">増加</button>
const calculator = {
value: 0,
increment: function() {
this.value++;
console.log("現在の値: " + this.value);
}
};
const button = document.getElementById("incrementButton");
// bindを使用しない場合
button.addEventListener("click", calculator.increment);
// クリック時に "現在の値: NaN" と出力される
// bindを使用する場合
button.addEventListener("click", calculator.increment.bind(calculator));
// クリック時に正しく "現在の値: 1", "現在の値: 2", ... と出力される
この例では、イベントリスナーにメソッドを渡す際にbind()
を使用することで、this
がcalculator
オブジェクトを正しく参照するようになります。
アロー関数と通常の関数におけるthis
の挙動とbind()
の使用
まずbind()の説明に入る前にthisの挙動について理解しておかないといけません。
初心者がないがしろにしがちなのですが、アロー関数は通常の関数とthisの扱いが異なります。
結論から言うと、通常の関数ではthisの値は呼び出し時に決定されますが、アロー関数では関数が定義された場所(レキシカルスコープ)のthisを引き継ぎます。
詳しく見ていきましょう。
1. 通常の関数のthis
通常の関数ではthis
の値は呼び出し時に決定されます。
function regularFunction() {
console.log(this.name);
}
const obj1 = { name: 'Alice', method: regularFunction };
const obj2 = { name: 'Bob' };
obj1.method(); // 出力: 'Alice'
regularFunction(); // 出力: undefined (strictモードでは error)
regularFunction.call(obj2); // 出力: 'Bob'
通常の関数は、呼び出し方によってthis
の値が変わります。
2. アロー関数のthis
アロー関数のthis
は、関数が定義された時点で決まり、後から変更できません。
const arrowFunction = () => {
console.log(this.name);
};
const obj1 = { name: 'Alice', method: arrowFunction };
const obj2 = { name: 'Bob' };
obj1.method(); // 出力: undefined
arrowFunction(); // 出力: undefined
arrowFunction.call(obj2); // 出力: undefined
アロー関数のthis
は、定義されたスコープのthis
を引き継ぎます。
つまり、上記の例ではグローバルスコープに定義されたアロー関数内のthisはグローバルオブジェクトを探しに行くので参照先(name)が見当たらずundefindとなっています。
ではどうやればうまくthisがnameを参照してくれるのでしょうか??
const obj = {
name: 'Alice',
arrowMethod: null,
init: function() {
this.arrowMethod = () => {
console.log(this.name);
};
}
};
obj.init();
obj.arrowMethod(); // 出力: 'Alice'
この例ではアロー関数がobjのメソッド内で定義されているため、objのthisがnameを参照してくれています。
3. bind()
の使用
3.1 通常の関数でのbind()
通常の関数では、bind()
を使ってthis
を固定できます。
function regularFunction() {
console.log(this.name);
}
const obj = { name: 'Charlie' };
const boundRegular = regularFunction.bind(obj);
boundRegular(); // 出力: 'Charlie'
3.2 アロー関数でのbind()
(誤った使用)
アロー関数にbind()
を使用しても、this
は変更されません。
const arrowFunction = () => {
console.log(this.name);
};
const obj = { name: 'Charlie' };
const boundArrow = arrowFunction.bind(obj);
boundArrow(); // 出力: undefined
つまり、アロー関数ではbind()を使用すべきではないということです。
それは先に説明した通り、thisの挙動が異なりbind()を使用したところで効果を発揮しないからです。
(探したら無理やり使う方法あるかも...)
3.3 アロー関数の正しい使用例
アロー関数は、外部スコープのthis
を利用したい場合に有用です:
const obj = {
name: 'Alice',
delayedGreeting: function() {
setTimeout(() => {
console.log('Hello, ' + this.name);
}, 1000);
}
};
obj.delayedGreeting(); // 1秒後に出力: 'Hello, Alice'
この例では、setTimeout
のコールバックとしてアロー関数を使用することで、obj
のthis
を正しく参照してくれています。
通常の関数とアロー関数とで比較する
const obj = {
name: 'Charlie',
arrowFunc: () => {
console.log(this.name);
},
regularFunc: function() {
console.log(this.name);
}
};
obj.arrowFunc(); // 出力: undefined (thisはグローバルスコープ)
obj.regularFunc(); // 出力: 'Charlie'
const boundArrow = obj.arrowFunc.bind(obj);
boundArrow(); // 出力: undefined (bindは効果なし)
const boundRegular = obj.regularFunc.bind({ name: 'Dave' });
boundRegular(); // 出力: 'Dave'
4. bind()
が必要な場合(通常の関数)
繰り返しになりますがbind()
が必要な場合は、通常の関数を使用するという考え方を持っておくのがベターです。
const user = {
name: 'Bob',
greet: function() {
console.log('Hello, ' + this.name);
}
};
const greet = user.greet;
greet(); // 出力: 'Hello, undefined'
const boundGreet = user.greet.bind(user);
boundGreet(); // 出力: 'Hello, Bob'
thisに関連する call()
とapply()
※初学者はスキップ可能
call()
とapply()
メソッドは、bind()と同様に関数の呼び出し方を制御し、this
の値を明示的に設定するために使用されます。
call()
メソッド
- 関数内で使用される
this
の値を明示的に設定する - 関数に引数を渡す
1. 基本的な構文
function.call(thisArg, arg1, arg2, ...)
-
thisArg
: 関数内でthis
として使用される値 -
arg1, arg2, ...
: 関数に渡す引数(複数可)
2. シンプルな例
まず、call()
を使わない通常の関数呼び出しを見てみましょう:
function greet() {
console.log('こんにちは、' + this.name + 'さん');
}
const person = { name: '太郎' };
// 通常の呼び出し
greet(); // 出力: "こんにちは、undefinedさん"
ここで、call()
を使用して this
を設定します:
greet.call(person); // 出力: "こんにちは、太郎さん"
3. 引数を渡す例
call()
は this
の設定だけでなく、関数に引数を渡すこともできます:
function introduce(hobby, age) {
console.log('私は' + this.name + 'です。' + hobby + 'が好きで、' + age + '歳です。');
}
const person = { name: '花子' };
introduce.call(person, '読書', 25);
// 出力: "私は花子です。読書が好きで、25歳です。"
4. call()
の実用的な使用例
4.1 配列のようなオブジェクトを本物の配列に変換
function convertToArray() {
return Array.prototype.slice.call(arguments);
}
const args = convertToArray(1, 2, 3);
console.log(args); // 出力: [1, 2, 3]
ここでは、Array.prototype.slice
メソッドをarguments
オブジェクト上で呼び出しています。
※上記方法はES6(ECMAScript 2015)以前によく使用されていましたが、現在では以下のような代替方法があります
function convertToArray(...args) {
return args;
}
function convertToArray() {
return Array.from(arguments);
}
4.2 メソッドの借用
const numbers = { 0: 10, 1: 20, 2: 30, length: 3 };
const sum = Array.prototype.reduce.call(numbers, (acc, curr) => acc + curr, 0);
console.log(sum); // 出力: 60
この例では、配列ではないオブジェクトに対して配列のメソッド(reduce
)を使用しています。こちらも同様にモダンJSでは以下のように記載します
const numbers = { 0: 10, 1: 20, 2: 30, length: 3 };
// Array.fromを使用する
const sum = Array.from(numbers).reduce((acc, curr) => acc + curr, 0);
// スプレッド構文を使用する
const sum = [...numbers].reduce((acc, curr) => acc + curr, 0);
5. call()
と通常の関数呼び出しの違い
-
this
の明示的な設定:
call()
を使用すると、関数内のthis
の値を明示的に設定できます。 -
関数の即時実行:
call()
は関数を即座に実行します。 -
引数の渡し方:
call()
では引数を個別に列挙して渡します。
6. call()まとめ
-
call()
メソッドは関数のthis
値を制御し、同時に引数を渡すための強力なツールです。 - 主に以下の場面で使用されます:
- オブジェクトのメソッドを他のオブジェクトで使用する
- 配列のようなオブジェクトに配列メソッドを適用する
- 関数の借用
-
call()
を理解し適切に使用することで、より柔軟で再利用可能なコードを書くことができます。
3. apply()
メソッド
3.1 基本的な使い方
apply()
メソッドはcall()
と似ていますが、引数を配列として渡します。
構文:function.apply(thisArg, [argsArray])
function greet(greeting) {
console.log(greeting + ', ' + this.name);
}
const person = { name: 'Charlie' };
greet.apply(person, ['Hello']); // 出力: "Hello, Charlie"
上記をcallを使用すると下記のようになります
greet.call(person, 'Hello');
3.2 複数の引数を使用する例
function introduce(greeting, profession) {
console.log(greeting + ', I\'m ' + this.name + ' and I\'m a ' + profession);
}
const person = { name: 'David' };
introduce.apply(person, ['Hi', 'teacher']);
// 出力: "Hi, I'm David and I'm a teacher"
4. call()
と apply()
の違い
主な違いは引数の渡し方です:
-
call()
: 引数を個別に渡します。 -
apply()
: 引数を配列として渡します。
function sum(a, b, c) {
return a + b + c;
}
console.log(sum.call(null, 1, 2, 3)); // 出力: 6
console.log(sum.apply(null, [1, 2, 3])); // 出力: 6
5. 実践的な使用例
5.1 配列の操作
const numbers = [5, 6, 2, 3, 7];
// Math.maxは通常、数値のみを引数に取りますが、
// applyを使用することで配列の要素を引数として渡せます
const max = Math.max.apply(null, numbers);
console.log(max); // 出力: 7
5.2 メソッドの借用
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// Array.prototypeのsliceメソッドを借用して配列に変換
const array = Array.prototype.slice.call(arrayLike);
console.log(array); // 出力: ['a', 'b', 'c']
こちらもモダンJSでは以下のように記載
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const array = Array.from(arrayLike);
console.log(array); // 出力: ['a', 'b', 'c']
6. 注意点
- アロー関数では、
call()
やapply()
を使用してもthis
の値は変更されません。 -
null
やundefined
をthisArg
として渡した場合、グローバルオブジェクト(ブラウザではwindow
)がthis
として使用されます(strictモードを除く)。
5. まとめ
-
通常の関数:
-
this
は呼び出し時に決定される -
bind()
,call()
,apply()
でthis
を変更できる - オブジェクトメソッドやイベントリスナーに適している
-
-
アロー関数:
-
this
は定義時に決定され、変更できない -
bind()
,call()
,apply()
はthis
に影響しない - コールバックやレキシカルな
this
が必要な場面に適している
-
適切な関数の種類を選ぶことで、this
の挙動に関する多くの問題を回避できます。状況に応じて、通常の関数とアロー関数を使い分けることが重要です。