考察下面的示例代碼:
class MyClass {
constructor(protected foo: string) {}
@MyDecorator
bar() {
console.log("bar");
}
}
function MyDecorator(
_target: any,
_key: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
// 🚨Property 'foo' does not exist on type 'PropertyDescriptor'.ts(2339)
console.log(this.foo);
return original.apply(this, args);
};
return descriptor;
}
const myClass = new MyClass("erm");
myClass.bar();
上面代碼定義了一個類 MyClass
,包含一個 protected
類型的 foo
屬性。
同時定義了一個 MyDecorator
裝飾器,在被裝飾方法調用前訪問上面的 protected foo
屬性並且打印出來。
可以看到上面示例中,已經將 TypeScript 報錯標識了出來,可以看到此時 this
所指的對象其實不對,指向了 PropertyDescriptor
,所以在裝飾器中試圖訪問 protected foo
時提示沒有 foo
屬性。
首先我們需要修正一下 this
的類型,因為該裝飾器修飾的是類的方法,所以 descriptor.value
中 this
應該是被修飾方法所在的類才對。
function MyDecorator(
_target: any,
_key: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
// 🚨Property 'foo' does not exist on type 'PropertyDescriptor'.ts(2339)
console.log((this as MyClass).foo);
return original.apply(this, args);
};
return descriptor;
}
當我們試圖通過強制類型轉換修正 this
的類型時,發現新的錯誤出現了。因為 foo
被聲明成了 protected
類型,它提示只能在 MyClass
中或其繼承類中訪問該屬性。但我們明確知道,運行時 descriptor.value
確實是在這個類當中的。同時 Hover 到強制類型轉換后的 this
上發現其類型還是 PropertyDescriptor
,說明強制類型轉換其實沒生效。
強制類型轉換失敗
this
入參
對於這種需要修正函數中 this
所指的場景,TypeScript 提供了一種機制,可以在函數入參列表中第一個位置處,手動寫入 this
標識其類型。但這個 this
入參只作為一個形式上的參數,供 TypeScript 做靜態檢查時使用,編譯后是不會存在於真實代碼中的。
function f(this: void) {
// make sure `this` is unusable in this standalone function
}
像上面這樣,f
被指定了 this
類型為 void
,即 f
這個函數的函數體內,不允許使用 this
。這有什么用呢,請看以下示例:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
constructor(public info: string) {}
onClickBad(this: Handler, e: Event) {
this.info = e.type;
}
}
let h = new Handler('foo');
// 🚨error
uiElement.addClickListener(h.onClickBad);
上面 uiElement.addClickListener
聲明了只接收一個不依賴於 this
上下文的函數做為回調,但我們傳入的 h.onClickBad
聲明為它執行時依賴於 Handler
這個上下文。因此顯式地修正函數的執行上下文可讓 TypeScript 檢查出相關的錯誤。
回到文章開頭的示例,我們就知道如何修正它了。
只需要將設置 descriptor.value
地方,為其添加上 this
入參即可保證正確的上下文了。
function MyDecorator(
_target: any,
_key: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
- descriptor.value = function(..args: any[]) {
+ descriptor.value = function(this: MyClass, ...args: any[]) {
console.log((this as MyClass).foo);
return original.apply(this, args);
};
return descriptor;
}