17ec084
@17ec084 (智剛 平田)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

関数リテラルとアロー関数における`this`の振る舞いの違い

解決したいこと

オブジェクトのキーにて関数リテラルまたはアロー関数内部でthisを使用すると、振る舞いが異なりました。
その理由を教えていただけますでしょうか。

具体的には、オブジェクト{i:1, f:function(){return this.i;}}とオブジェクト{i:1, f:()=>this.i}
が異なる機能を持つのはなぜでしょうか。

該当するソースコード

(paizaからご確認いただくこともできます。)

javascript
var obj1 = {i:1, f:function(){return this.i;}};
var obj2 = {i:1, f:()=>this.i}
console.log("obj1.f() == " + obj1.f())
console.log("obj2.f() == " + obj2.f())

出力結果

console
obj1.f() == 1
obj2.f() == undefined
0

1Answer

上手く説明できるかどうか分かりませんが、functionの方が古くからある記法で、function内で定義したthisは、呼び出し時に(誰がその関数をcallしたかによって)thisの中身が変わります。例えばcallapplyを使用して、thisの中身を後から変えることもできます。

var obj1 = {i:1, f:function(){return this.i;}};

console.log(obj1.f()); // obj1が呼び出しているので、thisにはobj1が入っています
// 1


window.i = 50
obj1.f.apply(window); // applyを使用してwindowのスコープでfを実行すると、thisはwindowに
// 50

この性質で混乱するのが、例えばsetTimeoutを呼んだ処理です。オブジェクト内で作成したSetTimeoutの引数である無名関数は、トップレベルでのcallになるのでwindowスコープとなり、期待とは違いthis.countwindow.countを指すことになります。

var obj = {
    count : 10,
    doSomethingLater : function (){
        setTimeout(function(){ // この関数のスコープはwindow
            this.count++;
            console.log(this.count);
        }, 300);
    }
}

obj.doSomethingLater(); // コンソールに "NaN" と表示。 "count" プロパティは window スコープではないため。

この問題に対して、従来は以下のようにbindなどでthisとなるスコープを拘束する必要がありました。

var obj = {
    count : 10,
    doSomethingLater : function (){
        setTimeout(function(){
            this.count++;
            console.log(this.count); // 無名関数のスコープを、doSomethingLaterのthisで拘束!
        }.bind(this), 300);
    }
}

obj.doSomethingLater();
// 約0.3秒後に11と表示

一方、アロー関数では作成時に(?ちょっとこの表現自信ないです)thisが決まります。これを使えば先ほどのsetTimeoutの例はシンプルにこう書けます。

var obj = {
    count : 10,
    doSomethingLater : function (){
        setTimeout(() => {
            this.count++;
            console.log(this.count);
        }, 300);
    }
}

obj.doSomethingLater();
// 約0.3秒後に11と表示

アロー関数の方が直感的なので使いやすいですが、例えばインスタンスメソッドには使えないなどいくつかの制約があります。

また、アロー関数自体をサポートしていないブラウザも僅かにあるのでご参考まで。アロー関数で書いたコードは、bindapplyなどを使ってアロー関数無しでも実現できるので、多くの場合開発時はアロー関数で書き、babelなどを使ってfunctionにトランスパイルすることが多いと思います。

1Like

Your answer might help someone💌