2021年12月27日 JavaScript (JS Primer) 関数とthis
thisが問題となるパターン
thisは所属するオブジェクトを直接書く代わりとして利用出来ますが、thisには大きく分けて2つの問題があります。
問題1: thisを含むメソッドを変数に代入した場合
問題2: コールバック関数とthis
本日は、1つ目の問題に触れていきます。
問題1: thisを含むメソッドを変数に代入した場合
thisは呼び出し時に定義されるため、基本的に呼び出し時にメソッドでなければundefinedになってしまいます。
以下の例ではpersonというベースオブジェクトから別のプロパティであるfullNameを参照するsayNameメソッドをsay変数に代入しています。
say変数を関数として呼び出す場合、実行時点でthisが定義されるためundefinedとなり、fullNameを参照出来ずにエラーが返っています。
"use strict"; const person = { fullName: "Brendan Eich", sayName: function() { // `this`は呼び出し元によって異なる return this.fullName; } }; // `sayName`メソッドは`person`オブジェクトに所属する // `this`は`person`オブジェクトとなる console.log(person.sayName()); // => "Brendan Eich" // `person.sayName`を`say`変数に代入する const say = person.sayName; // 代入したメソッドを関数として呼ぶ // この`say`関数はどのオブジェクトにも所属していない // `this`はundefinedとなるため例外を投げる say(); // => TypeError: Cannot read property 'fullName' of undefined
問題1の対処法
上記の問題の対処法はcall、apply、bindメソッドが挙げられます。
いずれも関数オブジェクトに用意されているメソッドで、this
を指定して関数を実行することが出来ます。
【問題1の対処法その1】 callメソッド
関数.call(thisの値, ...関数の引数);
callメソッドは第一引数にthisとしたい値を指定し、第二引数には呼び出す関数の引数を指定します。
以下のsay関数はpersonオブジェクトをthisで参照して実行するためにcallメソッドを使用している例です。
"use strict"; function say(message) { return `${message} ${this.fullName}!`; } const person = { fullName: "Brendan Eich" }; // `this`を`person`にして`say`関数を呼びだす console.log(say.call(person, "こんにちは")); // => "こんにちは Brendan Eich!" // `say`関数をそのまま呼び出すと`this`は`undefined`となるため例外が発生 say("こんにちは"); // => TypeError: Cannot read property 'fullName' of undefined
【問題1の対処法その2】 applyメソッド
- applyメソッドは第一引数にthisとする値を指定し、第二引数に関数の引数を配列として渡します。
やっていること自体はcallメソッドと同じで第二引数を配列を指定するかの違いがあるのみです。
以下の例では第二引数で指定した配列は、自動的に展開されてsay関数の仮引数messageに入ります。
"use strict"; function say(message, konbanha) { return `${konbanha} ${this.fullName}!`; } const person = { fullName: "Brendan Eich" }; // `this`を`person`にして`say`関数を呼びだす // callとは異なり引数を配列として渡す console.log(say.apply(person, ["こんにちは", "こんばんは"])); // => "こんにちは Brendan Eich!" // `say`関数をそのまま呼び出すと`this`は`undefined`となるため例外が発生 say("こんにちは"); // => TypeError: Cannot read property 'fullName' of undefined
また、どちらのメソッドもthis
の値が不要な場合はnull
を渡すのが一般的です。
【問題1の対処法その3】 bindメソッド
thisの値を束縛(bind)した新しい関数を作成します。
関数.bind(thisの値, ...関数の引数); // => thisや引数がbindされた関数
function say(message) { return `${message} ${this.fullName}!`; } const person = { fullName: "Brendan Eich" }; // `this`を`person`に束縛した`say`関数をラップした関数を作る const sayPerson = say.bind(person, "こんにちは"); console.log(sayPerson()); // => "こんにちは Brendan Eich!"
基本的にはメソッドを呼び出す事でこの問題を回避した方がよく、その上でどうしてもthis
を固定したいときにcall
apply
bind
メソッドを使うと良いでしょう。