我是TypeScript的初學(xué)者,在閱讀TypeScript官方手冊指南的<函數(shù)>一章在<this參數(shù)在回調(diào)函數(shù)里>這一小節(jié)產(chǎn)生了疑惑
官方的文檔提供的實例本身邏輯和運行結(jié)果都沒有問題,但是我自己做了幾個額外的測試,卻產(chǎn)生了一些意想不到的問題.
開發(fā)環(huán)境:
VSCode 1.26.1 TypeScript 3.0.1
這個例子用于在回調(diào)函數(shù)中如何提供this正確的類型判斷
定義一個回調(diào)接口:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
定義一個回調(diào)類,該類提供一個方法當作回調(diào)函數(shù):
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use this here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);// uiElement在官方例子中就沒有定義,我自己利用他提供了一個實現(xiàn)類
由于缺少了一個實現(xiàn)接口的類,所以我自己定義了一個類且實現(xiàn)了UIElement接口,問題也是由這個引起的.
我的完整例子:
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void): void {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood(this:void,event:string):void{
console.log('clicked');
console.log(event);
}
}
let h: Handler = new Handler();
let uiElement: SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)
這個例子是可以運行且沒有錯誤的,我提供了一個SideBar類,唯一的改動是回調(diào)函數(shù)中Event的類型改為了String.
例子鏈接:
https://www.tslang.cn/docs/ha...
例子在頁面下方的位置
一個類實現(xiàn)了接口定義的獲取回調(diào)函數(shù)的方法,類實現(xiàn)的方法有可能不校驗其內(nèi)部的參數(shù)結(jié)構(gòu)(回調(diào)的具體類型)
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick){
onclick('event');
}
}
這樣寫是沒有問題的,即使SideBar中的addClickListener方法沒有提供完整的類型驗證,但是TypeScript會自動推測出來,證據(jù)如下
class SideBar implements UIElement {
addClickListener(onclick,hello){
onclick('event');
}
}
我給這個方法多添加了一個參數(shù),由于UIElement中沒有定義,所以這里提示錯誤了,證明TypeScript的類型推斷工作正常,這符合我們的預(yù)期,但是接下來他卻工作的不正常了
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick){
onclick('event');
}
}
class Handler {
info: string;
onClickGood(this:Handler,event:string):void{
console.log('clicked!');
console.log(event);
}
}
let h:Handler = new Handler();
let uiElement:SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)// 沒有報錯
在這個例子的最后一句中我們將Handler類的onClickGood方法傳遞給了SideBar的addClickListener
請注意,如果按照之前結(jié)構(gòu)匹配的正常運轉(zhuǎn)效果,這里回調(diào)函數(shù)的接受類型應(yīng)該是onclick: (this: void, e: string) => void
但是我們傳遞過去的類型卻是(this:Handler,event:string) => void顯然不是正確的,但是卻沒有報錯.
只有給SideBar強制添加和UIElement接口一樣的類型規(guī)則情況下才提示類型不匹配
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void):void{
onclick('event');
}
}
class Handler {
info: string;
onClickGood(this:Handler,event:string):void{
// can't use this here because it's of type void!
console.log('clicked!');
console.log(event);
}
}
let h:Handler = new Handler();
let uiElement:SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)// 報錯了類型不匹配
被接口定義的回調(diào)函數(shù)的返回值實際實現(xiàn)可以和接口定義的類型不一致
繼續(xù)使用之前的例子:
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void) :void{
onclick('event');
}
}
class Handler {
info: string;
onClickGood(this:void,event:string){
console.log('clicked!');
console.log(event);
return 123;
}
}
let h:Handler = new Handler();
let uiElement:SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)// 沒有報錯
注意這里的Handler中的onClickGood他返回的是number類型,編輯器提示也是number類型,但是卻可以通過最后一句的測試.
即使我們addClickListener要求提供的函數(shù)的返回值為void.
除非給onClickGood也添加返回類型:
class Handler {
info: string;
onClickGood(this:void,event:string):void{
// can't use this here because it's of type void!
console.log('clicked!');
console.log(event);
return 123;
}
}
這下return的值終于被判定為錯誤了,但是這么一來addClickListener中制定的回調(diào)函數(shù)類型規(guī)則豈不是被無視了
--strict看到題主let h: Handler = new Handler()的寫法,我估計題主來自Java背景。JavaScript全是自動推斷,c++11也有auto關(guān)鍵字,只有Java拖到了今年3月才給了var。右邊的類型是確定的,左邊還要再多寫一個類型聲明,麻煩到這種程度的寫法也就Java有了。
Typescript希望在嚴謹和方便之間取得平衡,而不是糾結(jié)于數(shù)學(xué)上的正確,所以允許了一些(微軟認為常用的)類型不兼容。如果想盡可能像Java一樣嚴格,可以打開編譯器的--strict選項。
class SideBar implements UIElement {
addClickListener(onclick, hello) {
onclick('event');
}
}
這段代碼會報錯,報的什么錯呢?
類型“SideBar”中的屬性“addClickListener”不可分配給基類型“UIElement”中的同一屬性。
不能將類型“(onclick: any, hello: any) => void”分配給類型“(onclick: (this: void, e: string) => void) => void”。
注意到兩個any了嗎?這里報錯,實際上是參數(shù)數(shù)量的錯誤,參數(shù)類型檢查被跳過了。SideBar中的addClickListener方法沒有提供完整的類型簽名,所以TypeScript直接不管參數(shù)類型了。
如果從嚴謹?shù)慕嵌葋砜?,很坑爹對吧。所以我們打開--strict。現(xiàn)在addClickListener(onclick)也會報錯了:
參數(shù)“onclick”隱式具有“any”類型。
如果說“接口都定義好了,參數(shù)還不能自動推斷出來嗎?”,確實是這樣的,畢竟Java的lambda已經(jīng)做到了。但總之TypeScript現(xiàn)在還不支持……微軟:你來咬我呀~
It's a feature, not a bug. 讓我們考慮
let foo: () => void;
foo = () => 1;
這是合法的。對Java來說不可理喻對不對。然而
TypeScript里的類型兼容性是基于結(jié)構(gòu)子類型的。 結(jié)構(gòu)類型是一種只使用其成員來描述類型的方式。 它正好與名義(nominal)類型形成對比。 (類型兼容性)
對于函數(shù)返回值,
類型系統(tǒng)強制源函數(shù)的返回值類型必須是目標函數(shù)返回值類型的子類型 (來源同上)
上面的定義太深奧了,講人話:可以多給,不能少給。() => void是什么意思?你別指望我會返回什么東西,但我到底會返回什么,不關(guān)你事。
為什么要這樣設(shè)計?考慮
let realData = { username: 'liqi0816', avatar: 'liqi0816.jpg', page: 'liqi0816.html' };
let mock = true;
function foo(): { username: string } {
if (mock) {
return { username: 'test'};
}
else {
return realData;
}
}
你可以指望我返回的東西有username屬性,但我到底會返回什么,不關(guān)你事。這樣很方便對不對?
所以回到題主的問題
addClickListener(onclick: (this: void, e: string) => void): void
人話:我不需要回調(diào)函數(shù)有返回值,就算有,我也保證不用,所以你愛返回啥返回啥。
放置了幾天無人回答,有可能寫的太難懂了,自己又查查文檔搗鼓了幾下,出現(xiàn)了一些思路
我們再次使用一下之前我提供的完整的例子:
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void): void {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood(this:void,event:string):void{
console.log('clicked');
console.log(event);
}
}
let h: Handler = new Handler();
let uiElement: SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)
如果不嚴格的說這實際上就是一個觀察著模式,我這里提供一分同樣功能的JS版本(沒有模擬繼承):
function SideBar(){
}
SideBar.prototype.addClickListener = function (callback){
callback('event');
}
function Handler(){
this.info = 'ok';
}
Handler.prototype.onClickGood = function (event){
console.log(event);
}
let handler = new Handler();
let sidebar = new SideBar();
sidebar.addClickListener(handler.onClickGood);
運行輸出:
event
此時在如果我們打印onClickGood內(nèi)部的this輸出的肯定是全局對象(沒有開啟嚴格模式)
但是一般在使用觀察者模式的時候,我們一般傳入的只是一個函數(shù)而已,不會去傳入一個對象的方法,如下:
window.addEventListener('click',function (event){
console.log(event)
})
這個才是回調(diào)函數(shù),回調(diào)函數(shù)沒有狀態(tài)this指向的就是全局.
原來的問題是如何在TypeScript中獲取正確的this類型,而解決的方法就是顯式的指定this的類型為void.
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class Handler {
info: string = 'ok';
onClickGood (this:void,event:string) :void {
console.log(event);
}
}
上面onClickGood方法和UIElement接口定義的addClickListener要求傳入的函數(shù)格式一致,然后你的this終于獲得了正確的類型指向.
但是可笑在TypeScript是有類型推斷的,這一點在官方手冊指南的接口一章有提及,我們只提供了UIElement卻沒有提供實現(xiàn)類,現(xiàn)在我們提供一個實現(xiàn)類:
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void): void {
onclick('event');
}
}
但是如果有正確的類型推斷,我們可以省略SideBar,addClickListener方法中的類型約束,但是去掉的結(jié)果就是該方法失去了類型檢查功能:
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick) {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood (this:Handler,event:string) :void {
console.log(event);
}
}
這是可以運行的但是onClickGood可不符合UIElement中addClickListener接受函數(shù)定義的類型.
實際上不僅僅是函數(shù),對于一個普通的類型來說工作的也有點不正常:
interface UIElement {
addClickListener(onclick:string): void;
}
class SideBar implements UIElement {
addClickListener(onclick) {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood (this:Handler,event:string) :void {
console.log(event);
}
}
這個例子中UIElement中的addClickListener接受類型直接改成了string這個例子依然可以通過檢查,當然他是不可以運行的.
進過一番折騰后,我找到了一個可以合理解釋這一切的原因.
首先:
類型推斷工作只能限制一層
interface UIElement {
addClickListener(onclick:string): void;
}
class SideBar implements UIElement {
addClickListener(onclick:number) {
}
}
這個例子中實現(xiàn)類工作類型為number但是接口規(guī)定為string,類型不對報錯了.
但是如果我們在提供一個類繼承SideBar:
interface UIElement {
addClickListener(onclick:string): void;
}
class SideBar implements UIElement {
addClickListener(onclick) {
}
}
class Menu extends SideBar {
addClickListener(onclick:number){
}
}
Menu即使改成了number類型也沒有問題.
anyany它輸入任何類型的子類而由于上一級類型以及是any了,后續(xù)的類型就可以指定任何類型了
類型推斷在函數(shù)接口上工作的很好,對于一般的接口而言工作的不是很理想.
例子完美情況下的類型推斷:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (src, sub) {
let result = src.search(sub);
return result > -1;
}
所以對于多重繼承來說類型推斷工作的不是很好,我們只能將類型寫全,不過對于回調(diào)函數(shù)我們完全可以定義一個函數(shù)接口,來簡化代碼量:
interface EventCallback {
(this: void, e: string):void;
}
interface UIElement {
addClickListener(onclick:EventCallback): void;
}
class SideBar implements UIElement {
addClickListener(onclick:EventCallback): void {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood(this: void, event: string): void {
console.log('clicked');
console.log(event);
}
}
let h: Handler = new Handler();
let uiElement: SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)北大青鳥APTECH成立于1999年。依托北京大學(xué)優(yōu)質(zhì)雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國IT技能型緊缺人才,是大數(shù)據(jù)專業(yè)的國家
達內(nèi)教育集團成立于2002年,是一家由留學(xué)海歸創(chuàng)辦的高端職業(yè)教育培訓(xùn)機構(gòu),是中國一站式人才培養(yǎng)平臺、一站式人才輸送平臺。2014年4月3日在美國成功上市,融資1
北大課工場是北京大學(xué)校辦產(chǎn)業(yè)為響應(yīng)國家深化產(chǎn)教融合/校企合作的政策,積極推進“中國制造2025”,實現(xiàn)中華民族偉大復(fù)興的升級產(chǎn)業(yè)鏈。利用北京大學(xué)優(yōu)質(zhì)教育資源及背
博為峰,中國職業(yè)人才培訓(xùn)領(lǐng)域的先行者
曾工作于聯(lián)想擔(dān)任系統(tǒng)開發(fā)工程師,曾在博彥科技股份有限公司擔(dān)任項目經(jīng)理從事移動互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍懿科技有限責(zé)任公司從事總經(jīng)理職務(wù)負責(zé)iOS教學(xué)及管理工作。
浪潮集團項目經(jīng)理。精通Java與.NET 技術(shù), 熟練的跨平臺面向?qū)ο箝_發(fā)經(jīng)驗,技術(shù)功底深厚。 授課風(fēng)格 授課風(fēng)格清新自然、條理清晰、主次分明、重點難點突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫,具有快速界面開發(fā)的能力,對瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁制作和網(wǎng)頁游戲開發(fā)。
具有10 年的Java 企業(yè)應(yīng)用開發(fā)經(jīng)驗。曾經(jīng)歷任德國Software AG 技術(shù)顧問,美國Dachieve 系統(tǒng)架構(gòu)師,美國AngelEngineers Inc. 系統(tǒng)架構(gòu)師。