6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

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 メソッドを使うと良いでしょう。

参考

JS primer 関数とthis