interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
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参数在回调函数中(用来警告函数赋值之后this丢失问题)
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
class UIElement implements UIElement {
addClickListener() {}
}
const uiElement = new UIElement();
// ❎赋值
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
}
}
// ✅赋值
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);
类型兼容性
问题
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
declare function require(moduleName: string): any;
import { ZipCodeValidator as Zip } from "./ZipCodeValidator";
if (needZipValidation) {
let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) { /* ... */ }
}
接口
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内,那我们应该如何操作如下:
一个接口可以继承多个接口,创建出多个接口的合成接口。
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
在上面的例子里,
SelectableControl
包含了Control
的所有成员,包括私有成员state
。 因为state
是私有成员,所以只能够是Control
的子类们才能实现SelectableControl
接口。 因为只有Control
的子类才能够拥有一个声明于Control
的私有成员state
,这对私有成员的兼容性是必需的。也就四自己在Image中补充private state也是没有用的,这个必须有Control的子类实现类
问题:构造函数也可以被标记成
protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承这个在接口部分就说明过construction属于类静态属性,既然是静态属性为什么能够被标记?🦉
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。函数
类型兼容性
并不是很理解这个,为什么将x赋值为y,就可以忽略s这个必传参数?
因为忽略额外的参数在JavaScript里是很常见的。 例如,
Array#forEach
给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的,多思考鸭子类型(结构性类型系统),赋值之后y的类型推断并不会变化,常见的使用场景如下:高级类型
TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词:
谓词为
parameterName is Type
这种形式,parameterName
必须是来自于当前函数签名里的一个参数名。TypeScript具有两种特殊的类型,
null
和undefined
,它们分别具有值null和undefined. 我们在[基础类型](./Basic Types.md)一节里已经做过简要说明。 默认情况下,类型检查器认为null
与undefined
可以赋值给任何类型。null
与undefined
是所有其它类型的一个有效值。--strictNullChecks
标记可以解决此错误:当你声明一个变量时,它不会自动地包含null
或undefined
。Symbols
除了用户定义的symbols,还有一些已经众所周知的内置symbols。 内置symbols用来表示语言内部的行为。例如:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
...
可以通过
console.log(Object[Symbol.hasInstance])
来访问native函数模块(外部模块)
TypeScript与ECMAScript 2015一样,任何包含顶级
import
或者export
的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的export =
和import = require()
加载方式CommonJS和AMD的环境里都有一个
exports
变量,这个变量包含了一个模块的所有导出内容。CommonJS和AMD的
exports
都可以被赋值为一个对象
, 这种情况下其作用就类似于 es6 语法里的默认导出,即export default
语法了。虽然作用相似,但是export default
语法并不能兼容CommonJS和AMD的exports
为了支持CommonJS和AMD的
exports
, TypeScript提供了export =
语法export =
语法定义一个模块的导出对象
。 这里的对象
一词指的是类,接口,命名空间,函数或枚举。若使用
export =
导出一个模块,则必须使用TypeScript的特定语法import module = require("module")
来导入此模块。有时候,你只想在某种条件下才加载某个模块。 在TypeScript里,使用下面的方式来实现它和其它的高级加载场景,我们可以直接调用模块加载器并且可以保证类型完全。
当初次进入基于模块的开发模式时,可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。
在组织方面,命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。命名空间对解决全局作用域里命名冲突来说是很重要的。
命名空间(内部模块)
命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。命名空间对解决全局作用域里命名冲突来说是很重要的。
模块解析
模块解析有两种策略,一种是通过 Classic,另一种是Node方式
你可以使用
--moduleResolution
标记来指定使用哪种模块解析策略。若未指定,那么在使用了--module AMD | System | ES2015
时的默认值为Classic,其它情况时则为Node正常来讲编译器会在开始编译之前解析模块导入。 每当它成功地解析了对一个文件
import
,这个文件被会加到一个文件列表里,以供编译器稍后处理。--noResolve
编译选项告诉编译器不要添加任何不是在命令行上传入的文件到编译列表。 编译器仍然会尝试解析模块,但是只要没有指定这个文件,那么它就不会被包含在内。exclude
列表里的模块还会被编译器使用tsconfig.json
将文件夹转变一个“工程” 如果不指定任何“exclude”
或“files”
,文件夹里的所有文件包括tsconfig.json
和所有的子目录都会在编译列表里。 如果你想利用“exclude”
排除某些文件,甚至你想指定所有要编译的文件列表,请使用“files”
。有些是被
tsconfig.json
自动加入的。 它不会涉及到上面讨论的模块解析。 如果编译器识别出一个文件是模块导入目标,它就会加到编译列表里,不管它是否被排除了。因此,要从编译列表中排除一个文件,你需要在排除它的同时,还要排除所有对它进行
import
或使用了///
指令的文件。声明合并
TypeScript中的声明会创建以下三种实体之一:命名空间,类型或值。 创建命名空间的声明会新建一个命名空间,它包含了用(.)符号来访问时使用的名字。 创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上。 最后,创建值的声明会创建在JavaScript输出中看到的值。只要不产生冲突就是合法的
命名空间合并
类和命名空间合并,用来保证类型安全
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
如果方法装饰器返回一个值,它会被用作方法的属性描述符。
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义
@required
装饰器添加了元数据实体把参数标记为必需的。@validate
装饰器把greet
方法包裹在一个函数里在调用原先的函数前验证函数参数。注解功能是 Google AtScript 团队提出的,但并不是语言标准的一部分。然而,Yehuda Katz 已经把 Decorator 已经作为了 ECMAScript 7 的一项提案。可以用于在开发的时候注解和修改类和属性。
三斜线指令
三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释
/// <reference types=""/>
与
///<reference path="">
指令相似,这个指令是用来声明 依赖的; 一个/// <reference types="">
指令则声明了对某个包的依赖/// <reference no-default-lib="true"/>
这个指令把一个文件标记成默认库。 你会在
lib.d.ts
文件和它不同的变体的顶端看到这个注释。这个指令告诉编译器在编译过程中不要包含这个默认库(比如,
lib.d.ts
)。 这与在命令行上使用--noLib
相似。还要注意,当传递了
--skipDefaultLibCheck
时,编译器只会忽略检查带有///
的文件。声明规范
这里有一种特殊的意义:
done
回调函数可能以1个参数或2个参数调用。 代码大概的意思是说这个回调函数不在乎是否有elapsedTime
参数, 但是不需要把这个参数当成可选参数来达到此目的 -- 因为总是允许提供一个接收较少参数的回调函数。回调函数总是可以忽略某个参数的,因此没必要为参数少的情况写重载。 参数少的回调函数首先允许错误类型的函数被传入,因为它们匹配第一个重载。