前陣子談過 TypeScript 使用 this 常遇到的問題(參考:TypeScript的this陷阱),經網友提醒,我才發現 TypeScript 對 () => { … } (Arrow Function Expression,箭頭函式表示式,索性簡稱「箭函式」XD )加了魔法:自動宣告 var _this = this ,並將 () => { … } 裡的 this 自動換成 _this。

前陣子重整一個中型網站,將 JavaScript 翻成 TypeScript,改寫不少原本用JavaScript function 模擬的 ViewModel 類別,不免觸及存取自身屬性或方法的情境。原本 ViewModel 裡用 var self = this 的方式解決 this 混淆問題(self 慣例源於Knockout,請參照 Managing ‘this’ 一節),起初我依賴箭函式(Arrow Function Expression)處理 this 置換,省下自行宣告 var self = this 的麻煩,但踩過一些地雷之後,最後仍決定回歸 var self = this 做法。

使用「箭函式 + this」我遇到比較容易混淆的情境有以下幾種

1.箭函式的 this 置換只適用在類別的函式宣告

module Blah {
  class Foo {
        name = "Foo";
        speak: () => void;
  }
  export class Boo {
      name = "Boo"
      test = () => {
          var foo = new Foo();
          foo.speak = () => {
              alert("Hi, I am " + this.name);
          }
          foo.speak();
      }
  }
}
 
var b = new Blah.Boo();
b.test();

以上案例有兩個物件 Foo 及 Boo,在 Boo 類別內宣告 test 箭函式,在其中建立 Foo 並指定其 speak 方法,寫成 foo.speak = () => { alert("Hi, I am " + this.name); },由於這段程式被放在 Boo.test() 中,雖然我們透過箭函式指定 foo.speak,但因為不在 Foo 類別內部,故 this 指向 Boo 的 Instance(執行個體),而非 Foo 的 Instance,執行結果將為 "Hi, I am Boo"。如想顯示 Foo 的名稱,在這個案例用 foo.name 取代 this.name 即可。Live Demo

2.箭函式內的 this 置換屬強制性,無法避免

考慮以下範例:

class blah {
    target: JQuery;
    constructor(t: JQuery) {
        this.target = t;
    }
    setColor = () => {
        this.target.each(() => {
            var o = $(this);
            o.css("color", o.text());
            o.text("Converted");
        });
    }
}
$("body").append("<div class='c'>red</div>");
var b = new blah($('.c'));
b.setColor();

我們在 setColor 箭函式中使用 jQuery.each(),並很時尚地也用箭函數寫 .each() 處理邏輯。很不幸的,以上程式無法正常運作,理由是在類別方法箭函式內的箭函式,this 也會被一併置換成類別物件的 Instance,如下圖所示。

箭函式中的 this 置換屬強制性,無法避免,但要克服很簡單,將箭函式改回傳統 function() { … } 即可:

    setColor = () => {
        this.target.each(function() {
            var o = $(this);
            o.css("color", o.text());
            o.text("Converted");
        });
    }

踩過幾次地雷,大概就能摸清楚 TypeScript 箭函式的 this 特性,幾乎不再誤用。但經一番琢磨,決定回歸自己宣告 var self = this,並在函式中使用 self 表示物件 Instance 的做法,不依賴 TypeScript 幫忙置換。理由是難免會遇到箭函式內穿插 $.each()、.click(function() { }) 等 this 代表其他物件、元素的場合,用 self 與 this 將二者明確區隔開,Z > B。


Comments

# by Kim

我目前遇到的狀況是在Directive回call Controller的方法,而在Controller的this會是Directive的this,所以會發生錯誤。後來找到國外的文章是使用Instance Method來解決 http://blog.icanmakethiswork.io/2014/04/typescript-instance-methods.html

# by Jeffrey

to Kim, 有具體一點的範例嗎?我好奇是否能用controllerMethod.apply(controllerInstance)解決,感覺又更簡單一點。( 參考:http://blog.darkthread.net/post-2009-04-10-js-func-apply.aspx )

# by Kim

Hi 黑大,此篇的問題2 http://note.kimx.info/2015/04/type-script-this.html

# by Jeffrey

to Kim, 謝謝回饋。在你的案例中,改用Isolated Scope:scope = { ceSupplr: "&" },然後在Directive內呼叫scope.ceSupplr({ supplrDto: result.data }),也能避開this混淆問題。

# by Kim

cool

# by 小風

最近在寫 TypeScript 用到 each 時也遇到這個坑,你的文章寫的很清楚,一看就懂了~

Post a comment