前陣子談過 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。