多说会ES6转TS非常容易,然后在学习文档过程中,还是有些地方描述不详细或者新的概念导致断断续续看了一周的时间,才把手册指南都看完。这里把一些个人觉得比较难理解的地方做一个总结,以加深记忆并希望给新入坑的小伙伴一点灵感把。毕竟我觉得文档跳跃性太大了,而且有些地方描述不是很详细,网上的资料也都是照抄官网文档的。当然本人水平有限,有可能有不对的地方,望见谅。
1.类型断言
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
这句话的意思其实是告诉我们-类型断言只是让我们通过手动类型转换,让编译器通过类型检查,对于运行时没有影响。两种写法:
let someValue: any = "this is a string";
let strLength1: number = (<string>someValue).length;
let strLength2: number = (someValue as string).length;
2.接口
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 });
你可能会争辩这个程序已经正确地类型化了,因为width
属性是兼容的,不存在color
属性,而且额外的colour
属性是无意义的。
然而,TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
如果参数为对象字面量的话,必须不能存在目标类型不包含的属性,不然会报错。当然你可以先用一个变量引用这个对象字面量,然后传入到调用参数里。当然还有类型断言和字符串索引签名来解决这个问题。
3.引索类型
共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用 100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。文档是这样写的:
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
这句话其实应该是这样理解的:number索引是string索引的一种特殊情况。所以number索引的返回值也必须是基于string索引类型的返回值的。所以数字索引的返回值必须是字符串索引返回值类型的子类型。
4.接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
接口继承类类型时,不会包括其实现部分,和接口声明一样类存在的成员一样。其实很好理解,接口本身就不该有实现的。而后半句的意思其实是,虽然Typescript核心是结构性子类型化,但是private和protected是一个例外。比较类型兼容时,它们必须都是来自同一处声明时,才能认为这两个类型是兼容的。
5.抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract
关键字并且可以包含访问修饰符。
抽象类可以包含成员的实现细节,但是抽象类的抽象方法必须在继承抽象类的派生类当中写出具体实现。并且抽象类在声明时不会被实例化,意思就是不会生成抽象类的类型。
6.this参数
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
箭头函数能保存函数创建时的 this
值,而不是调用时的值。TypeScript会警告你犯了一个错误,如果你给编译器设置了--noImplicitThis
标记。 它会指出this.suits[pickedSuit]
里的this
的类型为any
。这是因为 this
来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的 this
参数。 this
参数是个假的参数,它出现在参数列表的最前面
this的类型为any的原因在于,箭头函数的this使用的上一层作用域的this,此例中为对象字面量的函数表达式里的this,而这个this指向的是一个对象字面量,没有对应的类型,返回的肯定是any了。其实只要为这个变量加一个类型接口就行了。箭头函数加不加this加参数其实无所谓,因为他绑定的就是这个对象字面量的this。
7.this参数在回调函数里
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = "123" }
}
let h = new Handler();
let uiElement: UIElement;
uiElement.addClickListener(h.onClickGood);
因为箭头函数不会捕获this
,所以你总是可以把它们传给期望this: void
的函数。 缺点是每个Handler
对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler
的原型链上。 它们在不同 Handler
对象间是共享的。
其实这句话的意思很简单。箭头函数的this是类实例,本身箭头函数的是没有this的。所以符合期望的this:void。至于后面说为什么每个Handler对象都会创建一个箭头函数,这是因为,JS不支持箭头函数作为类的方法,可以通过编译后的内容看出,TS是利用var _this=this;来实现箭头函数的,并且只能在constructor方法里实现的,所以会创建多个箭头函数。