[转]TypeScript中文文档——通用类型 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
介绍
这节介绍TypeScript里的类型推论。即,类型是在哪里如何被推断的。
基础
TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。如下面的例子
let x = 3;
变量x
的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。
大多数情况下,类型推论是直截了当地。 后面的小节,我们会浏览类型推论时的细微差别。
最佳通用类型
当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。例如,
let x = [0, 1, null];
为了推断x
的类型,我们必须考虑所有元素的类型。 这里有两种选择:number
和null
。 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。
由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。例如:
let zoo = [new Rhino(), new Elephant(), new Snake()];
这里,我们想让zoo被推断为Animal[]
类型,但是这个数组里没有对象是Animal
类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型:
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
如果没有找到最佳通用类型的话,类型推论的结果是空对象类型,{}
。 因为这个类型没有任何成员,所以访问其成员的时候会报错。
上下文类型
TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。比如:
window.onmousedown = function(mouseEvent) { console.log(mouseEvent.buton); //<- Error };
这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown
函数的类型来推断右边函数表达式的类型。 因此,就能推断出mouseEvent
参数的类型了。 如果函数表达式不是在上下文类型的位置,mouseEvent
参数的类型需要指定为any
,这样也不会报错了。
如果上下文类型表达式包含了明确的类型信息,上下文的类型被忽略。 重写上面的例子:
window.onmousedown = function(mouseEvent: any) { console.log(mouseEvent.buton); //<- Now, no error is given };
这个函数表达式有明确的参数类型注解,上下文类型被忽略。 这样的话就不报错了,因为这里不会使用到上下文类型。
上下文归类会在很多情况下使用到。 通常包含函数的参数,赋值表达式的右边,类型断言,对象成员和数组字面量和返回值语句。 上下文类型也会做为最佳通用类型的候选类型。比如:
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
这个例子里,最佳通用类型有4个候选者:Animal
,Rhino
,Elephant
和Snake
。 当然,Animal
会被做为最佳通用类型。
[转]TypeScript中文文档——类型兼容性 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。) 看下面的例子:
在使用基于名义类型的语言,比如C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。
TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。
TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。
TypeScript结构化类型系统的基本规则是,如果
这里要检查
检查函数参数时使用相同的规则:
注意,
这个比较过程是递归进行的,检查每个成员及子成员。
相对来讲,在比较原始类型和对象类型的时候是比较容易理解的,问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手,它们仅是参数列表略有不同:
要查看
第二个赋值错误,因为
你可能会疑惑为什么允许
下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数:
类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。
当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。例如:
比较函数兼容性的时候,可选参数与必须参数是可交换的。 原类型上额外的可选参数并不会造成错误,目标类型的可选参数没有对应的参数也不是错误。
当一个函数有剩余参数时,它被当做无限个可选参数。
这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些
有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用:
对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,
类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。
私有成员会影响兼容性判断。 当类的实例用来检查兼容时,如果它包含一个私有成员,那么目标类型必须包含来自同一个类的这个私有成员。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如,
上面代码里,
在这里,泛型类型在使用时就好比不是一个泛型类型。
对于没指定泛型类型的泛型参数时,会把所有泛型参数当成
比如,
目前为止,我们使用了
语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的甚至在
介绍
interface Named {
name: string;
} class Person {
name: string;
} let p: Named; // OK, because of structural typing p = new Person();
关于可靠性的注意事项
开始
x
要兼容y
,那么y
至少具有与x
相同的属性。比如:
interface Named {
name: string;
} let x: Named; // y's inferred type is { name: string; location: string; } let y = { name: 'Alice', location: 'Seattle' };
x = y;
y
是否能赋值给x
,编译器检查x
中的每个属性,看是否能在y
中也找到对应属性。 在这个例子中,y
必须包含名字是name
的string
类型成员。y
满足条件,因此赋值正确。
function greet(n: Named) {
alert('Hello, ' + n.name);
}
greet(y); // OK
y
有个额外的location
属性,但这不会引发错误。 只有目标类型(这里是Named
)的成员会被一一检查是否兼容。
比较两个函数
let x = (a: number) => 0; let y = (b: number, s: string) => 0;
y = x; // OK x = y; // Error
x
是否能赋值给y
,首先看它们的参数列表。 x
的每个参数必须能在y
里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。 这里,x
的每个参数在y
中都能找到对应的参数,所以允许赋值。
y
有个必需的第二个参数,但是x
并没有,所以不允许赋值。
忽略
参数,像例子y = x
中那样。 原因是忽略额外的参数在JavaScript里是很常见的。 例如,Array#forEach
给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的:
let items = [1, 2, 3]; // Don't force these extra arguments items.forEach((item, index, array) => console.log(item)); // Should be OK! items.forEach((item) => console.log(item));
let x = () => ({name: 'Alice'}); let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK y = x; // Error because x() lacks a location property
函数参数双向协变
enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));
可选参数及剩余参数
undefinded
。
function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}
// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
函数重载
枚举
enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let status = Status.Ready;
status = Color.Green; //error
类
class Animal {
feet: number; constructor(name: string, numFeet: number) { }
} class Size {
feet: number; constructor(numFeet: number) { }
} let a: Animal; let s: Size;
a = s; //OK s = a; //OK
类的私有成员
泛型
interface Empty<T> {
} let x: Empty<number>; let y: Empty<string>;
x = y; // okay, y matches structure of x
x
和y
是兼容的,因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下,增加一个成员,就能看出是如何工作的了:
interface NotEmpty<T> {
data: T;
} let x: NotEmpty<number>; let y: NotEmpty<string>;
x = y; // error, x and y are not compatible
any
比较。 然后用结果类型进行比较,就像上面第一个例子。
let identity = function<T>(x: T): T { // ... } let reverse = function<U>(y: U): U { // ... }
identity = reverse; // Okay because (x: any)=>any matches (y: any)=>any
高级主题
子类型与赋值
兼容性
,它在语言规范里没有定义。 在TypeScript里,有两种类型的兼容性:子类型与赋值。 它们的不同点在于,赋值扩展了子类型兼容,允许给any
赋值或从any
取值和允许数字赋值给枚举类型或枚举类型赋值给数字。
implements
和extends
语句里。 更多信息,请参阅TypeScript语言规范.
[转]TypeScript中文文档——高级类型 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
偶尔你会遇到这种情况,一个代码库希望传入
在传统的面向对象语言里,我们可能会将这两种类型抽象成有层级的类型。 这么做显然是非常清晰的,但同时也存在了过度设计。
代替
联合类型表示一个值可以是几种类型之一。 我们用竖线(
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
这里的联合类型可能有点复杂,但是你很容易就习惯了。 如果一个值类型是
联合类型非常适合这样的情形,可接收的值有不同的类型。 当我们想明确地知道是否拿到
为了让这段代码工作,我们要使用类型断言:
可以注意到我们使用了多次类型断言。 如果我们只要检查过一次类型,就能够在后面的每个分支里清楚
TypeScript里的类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型断言:
在这个例子里,
每当使用一些变量调用
注意TypeScript不仅知道在
我们还没有真正的讨论过如何使用联合类型来实现
然而,必须要定义一个函数来判断类型是否是原始类型,这太痛苦了。 幸运的是,现在我们不必将
这些
如果你已经阅读了
交叉类型与联合类型密切相关,但是用法却完全不同。 一个交叉类型,例如
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:
我们也可以使用类型别名来在属性里引用自己:
然而,类型别名不能够出现在声名语句的右侧:
像我们提到的,类型别名可以像接口一样;然而,仍有一些细微差别。
一个重要区别是类型别名不能被
另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
字符串字面量类型允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。
你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误。
字符串字面量类型还可以用于区分函数重载:
多态的
由于这个类使用了
如果没有
联合类型
number
或string
类型的参数。 例如下面的函数:
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/ function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;
} if (typeof padding === "string") { return padding + value;
} throw new Error(`Expected string or number, got '${padding}'.`);
}
padLeft("Hello world", 4); // returns " Hello world"
padLeft
存在一个问题,padding
参数的类型指定成了any
。 这就是说我们可以传入一个既不是number
也不是string
类型的参数,但是TypeScript却不报错。
let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错
padLeft
原始版本的好处之一是允许我们传入原始类型。 这做的话使用起来既方便又不过于繁锁。 如果我们就是想使用已经存在的函数的话,这种新的方式就不适用了。
any
, 我们可以使用联合类型做为padding
的参数:
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/ function padLeft(value: string, padding: string | number) { // ... } let indentedString = padLeft("Hello world", true); // errors during compilation
|
)分隔每个类型,所以number | string | boolean
表示一个值可以是number
,string
,或boolean
。
interface Bird {
fly();
layEggs();
} interface Fish {
swim();
layEggs();
} function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet();
pet.layEggs(); // okay pet.swim(); // errors
A | B
,我们只能确定它具有成员同时存在于A
和B
里。 这个例子里,Bird
具有一个fly
成员。 我们不能确定一个Bird | Fish
类型的变量是否有fly
方法。 如果变量在运行时是Fish
类型,那么调用pet.fly()
就出错了。
类型保护与区分类型
Fish
时会怎么做? JavaScript里常用来区分2个可能值的方法是检查它们是否存在。 像之前提到的,我们只能访问联合类型的所有类型中共有的成员。
let pet = getSmallPet(); // 每一个成员访问都会报错 if (pet.swim) {
pet.swim();
} else if (pet.fly) {
pet.fly();
}
let pet = getSmallPet(); if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
用户自定义的类型保护
pet
的类型的话就好了。
function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined;
}
pet is Fish
就是类型断言。 一个断言是parameterName is Type
这种形式,parameterName
必须是来自于当前函数签名里的一个参数名。
isFish
时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
// 'swim' 和 'fly' 调用都没有问题了 if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
if
分支里pet
是Fish
类型; 它还清楚在else
分支里,一定不是Fish
类型,一定是Bird
类型。
typeof
类型保护
padLeft
。 我们可以像下面这样利用类型断言来写:
function isNumber(x: any): x is number { return typeof x === "number";
} function isString(x: any): x is string { return typeof x === "string";
} function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value;
} if (isString(padding)) { return padding + value;
} throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof x === "number"
抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。
function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;
} if (typeof padding === "string") { return padding + value;
} throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof
类型保护只有2个形式能被识别:typeof v === "typename"
和typeof v !== "typename"
,"typename"
必须是"number"
,"string"
,"boolean"
或"symbol"
。 但是TypeScript并不会阻止你与其它字符串比较,或者将它们位置对换,且语言不会把它们识别为类型保护。
instanceof
类型保护
typeof
类型保护并且对JavaScript里的instanceof
操作符熟悉的话,你可能已经猜到了这节要讲的内容。
instanceof
类型保护是通过其构造函数来细化其类型。 比如,我们借鉴一下之前字符串填充的例子:
interface Padder {
getPaddingString(): string } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) { }
getPaddingString() { return Array(this.numSpaces + 1).join(" ");
}
} class StringPadder implements Padder { constructor(private value: string) { }
getPaddingString() { return this.value;
}
} function getRandomPadder() { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(" ");
} // 类型为SpaceRepeatingPadder | StringPadder let padder: Padder = getRandomPadder(); if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder' } if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder' }
instanceof
的右侧要求为一个构造函数,TypeScript将细化为:
prototype
属性,如果它的类型不为any
的话
交叉类型
Person & Serializable & Loggable
,同时是Person
和Serializable
和Loggable
。 就是说这个类型的对象同时拥有这三种类型的成员。 实际应用中,你大多会在混入中见到交叉类型。 下面是一个混入的例子:
function extend<T, U>(first: T, second: U): T & U { let result = <T & U>{}; for (let id in first) {
(<any>result)[id] = (<any>first)[id];
} for (let id in second) { if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
} return result;
} class Person { constructor(public name: string) { }
} interface Loggable {
log(): void;
} class ConsoleLogger implements Loggable {
log() { // ... }
} var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name;
jim.log();
类型别名
type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n;
} else { return n();
}
}
type Container<T> = { value: T };
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
type Yikes = Array<Yikes>; // 错误
接口 vs. 类型别名
extends
和implements
也不能去extends
和implements
其它类型。 因为软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。
字符串字面量类型
type Easing = "ease-in" | "ease-out" | "ease-in-out"; class UIElement {
animate(dx: number, dy: number, easing: Easing) { if (easing === "ease-in") { // ... } else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else { // error! should not pass null or undefined. }
}
} let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"'
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
多态的
this
类型
this
类型表示的是某个包含类或接口的子类型。 这被称做F-bounded多态性。 它能很容易的表现连贯接口间的继承,比如。 在计算器的例子里,在每个操作之后都返回this
类型:
class BasicCalculator { public constructor(protected value: number = 0) { } public currentValue(): number { return this.value;
} public add(operand: number): this { this.value += operand; return this;
} public multiply(operand: number): this { this.value *= operand; return this;
} // ... other operations go here ... } let v = new BasicCalculator(2)
.multiply(5)
.add(1)
.currentValue();
this
类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。
class ScientificCalculator extends BasicCalculator { public constructor(value = 0) { super(value);
} public sin() { this.value = Math.sin(this.value); return this;
} // ... other operations go here ... } let v = new ScientificCalculator(2)
.multiply(5)
.sin()
.add(1)
.currentValue();
this
类型,ScientificCalculator
就不能够在继承BasicCalculator
的同时还保持接口的连贯性。 multiply
将会返回BasicCalculator
,它并没有sin
方法。 然而,使用this
类型,multiply
会返回this
,在这里就是ScientificCalculator
。
[转]TypeScript中文文档——迭代 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
当一个对象实现了
下面的例子展示了两者之间的区别:
另一个区别是
当生成目标为ES5或ES3,迭代器只允许在
编译器会生成一个简单的
生成的代码为:
当目标为兼容ECMAScipt 2015的引擎时,编译器会生成相应引擎的
可迭代性
Symbol.iterator
属性时,我们认为它是可迭代的。 一些内置的类型如Array
,Map
,Set
,String
,Int32Array
,Uint32Array
等都已经实现了各自的Symbol.iterator
。 对象上的Symbol.iterator
函数负责返回供迭代的值。
for..of
语句
for..of
会遍历可迭代的对象,调用对象上的Symbol.iterator
方法。 下面是在数组上使用for..of
的简单例子:
let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, "string", false }
for..of
vs. for..in
语句
for..of
和for..in
均可迭代一个列表;但是用于迭代的值却不同,for..in
迭代的是对象的 键 的列表,而for..of
则迭代对象的键对应的值。
let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2", } for (let i of list) { console.log(i); // "4", "5", "6" }
for..in
可以操作任何对象;它提供了查看对象属性的一种方法。 但是for..of
关注于迭代对象的值。内置对象Map
和Set
已经实现了Symbol.iterator
方法,让我们可以访问它们保存的值。
let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals"; for (let pet in pets) { console.log(pet); // "species" } for (let pet of pets) { console.log(pet); // "Cat", "Dog", "Hamster" }
代码生成
目标为 ES5 和 ES3
Array
类型上使用。 在非数组值上使用for..of
语句会得到一个错误,就算这些非数组值已经实现了Symbol.iterator
属性。
for
循环做为for..of
循环,比如:
let numbers = [1, 2, 3]; for (let num of numbers) { console.log(num);
}
var numbers = [1, 2, 3]; for (var _i = 0; _i < numbers.length; _i++) { var num = numbers[_i]; console.log(num);
}
目标为 ECMAScript 2015 或更高
for..of
内置迭代器实现方式。
[转]TypeScript中文文档——模块 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。
模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用
模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。
模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于Node.js的CommonJS和服务于Web应用的Require.js。
TypeScript与ECMAScript 2015一样,任何包含顶级
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加
导出语句很便利,因为我们可能需要对导出的部分重命名,所以上面的例子可以这样改写:
我们经常会去扩展其它模块,并且只导出那个模块的部分内容。 重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。
或者一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:
模块的导入操作与导出一样简单。 可以使用以下
可以对导入内容重命名
尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:
每个模块都可以有一个
类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。
或者
CommonJS和AMD都有一个
它们也支持把
若要导入一个使用了
根据编译时指定的模块目标参数,编译器会生成相应的供Node.js (CommonJS),Require.js (AMD),isomorphic (UMD), SystemJS或ECMAScript 2015 native modules (ES6)模块加载系统使用的代码。 想要了解生成代码中
下面的例子说明了导入导出语句里使用的名字是怎么转换为相应的模块加载器代码的。
下面我们来整理一下前面的验证器实现,每个模块只有一个命名的导出。
为了编译,我们必需要在命令行上指定一个模块目标。对于Node.js来说,使用
编译完成后,每个模块会生成一个单独的
有时候,你只想在某种条件下才加载某个模块。 在TypeScript里,使用下面的方式来实现它和其它的高级加载场景,我们可以直接调用模块加载器并且可以保证类型完全。
编译器会检测是否每个模块都会在生成的JavaScript中用到。 如果一个模块标识符只在类型注解部分使用,并且完全没有在表达式中使用时,就不会生成
这种模式的核心是
为了确保类型安全性,我们可以使用
要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。
我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在
在Node.js里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的
现在我们可以
假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。
简写模块里所有导出的类型将是
某些模块加载器如SystemJS 和AMD支持导入非JavaScript内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。
现在你可以就导入匹配
有些模块被设计成兼容多个模块加载器,或者不使用模块加载器(全局变量)。 它们以UMD或Isomorphic模块为代表。 这些库可以通过导入的形式或全局变量的形式访问。 例如:
之后,这个库可以在某个模块里通过导入来使用:
它同样可以通过全局变量的形式使用,但只能在某个脚本里。 (脚本是指一个不带有导入或导出的文件。)
用户应该更容易地使用你模块导出的内容。 嵌套层次过多会变得难以处理,因此仔细考虑一下如何组织你的代码。
从你的模块中导出一个命名空间就是一个增加嵌套的例子。 虽然命名空间有时候有它们的用处,在使用模块的时候它们额外地增加了一层。 这对用户来说是很不便的并且通常是多余的。
导出类的静态方法也有同样的问题 - 这个类本身就增加了一层嵌套。 除非它能方便表述或便于清晰使用,否则请考虑直接导出一个辅助方法。
就像“在顶层上导出”帮助减少用户使用的难度,一个默认的导出也能起到这个效果。 如果一个模块就是为了导出特定的内容,那么你应该考虑使用一个默认导出。 这会令模块的导入和使用变得些许简单。 比如:
对用户来说这是最理想的。他们可以随意命名导入模块的类型(本例为
相反地,当导入的时候:
你可能经常需要去扩展一个模块的功能。 JS里常用的一个模式是JQuery那样去扩展原对象。 如我们之前提到的,模块不会像全局命名空间对象那样去合并。 推荐的方案是不要去改变原来的对象,而是导出一个新的实体来提供新的功能。
假设
这是使用导出的
现在扩展它,添加支持输入其它进制(十进制以外),让我们来创建
新的
当初次进入基于模块的开发模式时,可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。
在组织方面,命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。 例如,在C#里,你会从
命名空间对解决全局作用域里命名冲突来说是很重要的。 比如,你可以有一个
更多关于模块和命名空间的资料查看命名空间和模块
以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使用命名空间:
module X {
相当于现在推荐的写法 namespace X {
)。
介绍
export
形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用import
形式之一。
import
或者export
的文件都被当成一个模块。
导出
导出声明
export
关键字来导出。
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
}
导出语句
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
} export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator };
重新导出
ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
isAcceptable(s: string) { return s.length === 5 && parseInt(s).toString() === s;
}
} // 导出原先的验证器但做了重命名 export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
export * from "module"
。
AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
导入
import
形式之一来导入其它模块中的导出内容。
导入一个模块中的某个导出内容
import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator();
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV();
将整个模块导入到一个变量,并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
具有副作用的导入模块
import "./my-module.js";
默认导出
default
导出。 默认导出使用default
关键字标记;并且一个模块只能够有一个default
导出。 需要使用一种特殊的导入形式来导入default
导出。
default
导出十分便利。 比如,像JQuery这样的类库可能有一个默认导出jQuery
或$
,并且我们基本上也会使用同样的名字jQuery
或$
导出JQuery。
JQuery.d.ts
declare let $: JQuery; export default $;
App.ts
import $ from "JQuery";
$("button.continue").html( "Next Step..." );
ZipCodeValidator.ts
export default class ZipCodeValidator { static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string) { return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
}
}
Test.ts
import validator from "./ZipCodeValidator"; let myValidator = new validator();
StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/; export default function (s: string) { return s.length === 5 && numberRegexp.test(s);
}
Test.ts
import validate from "./StaticZipCodeValidator"; let strings = ["Hello", "98052", "101"]; // Use function validate strings.forEach(s => { console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});
default
导出也可以是一个值
OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree"; console.log(num); // "123"
export =
和 import = require()
exports
对象的概念,它包含了一个模块的所有导出内容。
exports
替换为一个自定义对象。 默认导出就好比这样一个功能;然而,它们却并不相互兼容。 TypeScript模块支持export =
语法以支持传统的CommonJS和AMD的工作流模型。
export =
语法定义一个模块的导出对象。 它可以是类,接口,命名空间,函数或枚举。
export =
的模块时,必须使用TypeScript提供的特定语法import let = require("module")
。
ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/; class ZipCodeValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
} export = ZipCodeValidator;
Test.ts
import zip = require("./ZipCodeValidator"); // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validator = new zip(); // Show whether each string passed each validator strings.forEach(s => { console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});
生成模块代码
define
,require
和 register
的意义,请参考相应模块加载器的文档。
SimpleModule.ts
import m = require("mod"); export let t = m.something + 1;
AMD / RequireJS SimpleModule.js
define(["require", "exports", "./mod"], function (require, exports, mod_1) {
exports.t = mod_1.something + 1;
});
CommonJS / Node SimpleModule.js
let mod_1 = require("./mod");
exports.t = mod_1.something + 1;
UMD SimpleModule.js
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { let v = factory(require, exports); if (v !== undefined) module.exports = v;
} else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./mod"], factory);
}
})(function (require, exports) { let mod_1 = require("./mod");
exports.t = mod_1.something + 1;
});
System SimpleModule.js
System.register(["./mod"], function(exports_1) { let mod_1; let t; return {
setters:[ function (mod_1_1) {
mod_1 = mod_1_1;
}],
execute: function() {
exports_1("t", t = mod_1.something + 1);
}
}
});
Native ECMAScript 2015 modules SimpleModule.js
import { something } from "./mod"; export let t = something + 1;
简单示例
--module commonjs
; 对于Require.js来说,使用`--module amd
。比如:
tsc --module commonjs Test.ts
.js
文件。 好比使用了reference标签,编译器会根据import
语句编译相应的文件。
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
LettersOnlyValidator.ts
import { StringValidator } from "./Validation"; const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) { return lettersRegexp.test(s);
}
}
ZipCodeValidator.ts
import { StringValidator } from "./Validation"; const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
}
Test.ts
import { StringValidator } from "./Validation"; import { ZipCodeValidator } from "./ZipCodeValidator"; import { LettersOnlyValidator } from "./LettersOnlyValidator"; // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator(); // Show whether each string passed each validator strings.forEach(s => { for (let name in validators) { console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
});
可选的模块加载和其它高级加载场景
require
这个模块的代码。 省略掉没有用到的引用对性能提升是很有益的,并同时提供了选择性加载模块的能力。
import id = require("...")
语句可以让我们访问模块导出的类型。 模块加载器会被动态调用(通过require
),就像下面if
代码块里那样。 它利用了省略引用的优化,所以模块只在被需要时加载。 为了让这个模块工作,一定要注意import
定义的标识符只能在表示类型处使用(不能在会转换成JavaScript的地方)。
typeof
关键字。 typeof
关键字,当在表示类型的地方使用时,会得出一个类型值,这里就表示模块的类型。
示例:Node.js里的动态模块加载
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("...")) { /* ... */ }
}
示例:require.js里的动态模块加载
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;
import * as Zip from "./ZipCodeValidator";
if (needZipValidation) {
require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
let validator = new ZipCodeValidator.ZipCodeValidator();
if (validator.isAcceptable("...")) { /* ... */ }
});
}
示例:System.js里的动态模块加载
declare const System: any; import { ZipCodeValidator as Zip } from "./ZipCodeValidator"; if (needZipValidation) {
System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => { var x = new ZipCodeValidator(); if (x.isAcceptable("...")) { /* ... */ }
});
}
使用其它的JavaScript库
.d.ts
文件里定义的。 如果你熟悉C/C++,你可以把它们当做.h
文件。 让我们看一些例子。
外部模块
export
声明来为每个模块都定义一个.d.ts
文件,但最好还是写在一个大的.d.ts
文件里。 我们使用与构造一个外部命名空间相似的方法,但是这里使用module
关键字并且把名字用引号括起来,方便之后import
。 例如:
node.d.ts (simplified excerpt)
declare module "url" { export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
} export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
} declare module "path" { export function normalize(p: string): string; export function join(...paths: any[]): string; export let sep: string;
}
/// <reference>
node.d.ts
并且使用import url = require("url");
加载模块。
/// <reference path="node.d.ts"/> import * as URL from "url"; let myUrl = URL.parse("http://www.typescriptlang.org");
外部模块简写
declarations.d.ts
declare module "hot-new-module";
any
。
import x, {y} from "hot-new-module";
x(y);
模块声明通配符
declare module "*!text" { const content: string; export default content;
} // Some do it the other way around. declare module "json!*" { const value: any; export default value;
}
"*!text"
或"json!*"
的内容了。
import fileContent from "./xyz.txt!text"; import data from "json!http://example.com/data.json"; console.log(data, fileContent);
UMD模块
math-lib.d.ts
export const isPrime(x: number): boolean; export as namespace mathLib;
import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module
mathLib.isPrime(2);
创建模块结构指导
尽可能地在顶层导出
如果仅导出单个
class
或 function
,使用 export default
MyClass.ts
export default class SomeType { constructor() { ... }
}
MyFunc.ts
export default function getThing() { return 'thing'; }
Consumer.ts
import t from "./MyClass"; import f from "./MyFunc"; let x = new t(); console.log(f());
t
)并且不需要多余的(.)来找到相关对象。
如果要导出多个对象,把它们放在顶层里导出
MyThings.ts
export class SomeType { /* ... */ } export function someFunc() { /* ... */ }
明确地列出导入的名字
Consumer.ts
import { SomeType, SomeFunc } from "./MyThings"; let x = new SomeType(); let y = someFunc();
使用命名空间导入模式当你要导出大量内容的时候
MyLargeModule.ts
export class Dog { ... } export class Cat { ... } export class Tree { ... } export class Flower { ... }
Consumer.ts
import * as myLargeModule from "./MyLargeModule.ts"; let x = new myLargeModule.Dog();
使用重新导出进行扩展
Calculator.ts
模块里定义了一个简单的计算器实现。 这个模块同样提供了一个辅助函数来测试计算器的功能,通过传入一系列输入的字符串并在最后给出结果。
Calculator.ts
export class Calculator { private current = 0; private memory = 0; private operator: string; protected processDigit(digit: string, currentValue: number) { if (digit >= "0" && digit <= "9") { return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));
}
} protected processOperator(operator: string) { if (["+", "-", "*", "/"].indexOf(operator) >= 0) { return operator;
}
} protected evaluateOperator(operator: string, left: number, right: number): number { switch (this.operator) { case "+": return left + right; case "-": return left - right; case "*": return left * right; case "/": return left / right;
}
} private evaluate() { if (this.operator) { this.memory = this.evaluateOperator(this.operator, this.memory, this.current);
} else { this.memory = this.current;
} this.current = 0;
} public handelChar(char: string) { if (char === "=") { this.evaluate(); return;
} else { let value = this.processDigit(char, this.current); if (value !== undefined) { this.current = value; return;
} else { let value = this.processOperator(char); if (value !== undefined) { this.evaluate(); this.operator = value; return;
}
}
} throw new Error(`Unsupported input: '${char}'`);
} public getResult() { return this.memory;
}
} export function test(c: Calculator, input: string) { for (let i = 0; i < input.length; i++) {
c.handelChar(input[i]);
} console.log(`result of '${input}' is '${c.getResult()}'`);
}
test
函数来测试计算器。
TestCalculator.ts
import { Calculator, test } from "./Calculator"; let c = new Calculator();
test(c, "1+2*33/11="); // prints 9
ProgrammerCalculator.ts
。
ProgrammerCalculator.ts
import { Calculator } from "./Calculator"; class ProgrammerCalculator extends Calculator { static digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; constructor(public base: number) { super(); if (base <= 0 || base > ProgrammerCalculator.digits.length) { throw new Error("base has to be within 0 to 16 inclusive.");
}
} protected processDigit(digit: string, currentValue: number) { if (ProgrammerCalculator.digits.indexOf(digit) >= 0) { return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit);
}
}
} // Export the new extended calculator as Calculator export { ProgrammerCalculator as Calculator }; // Also, export the helper function export { test } from "./Calculator";
ProgrammerCalculator
模块导出的API与原先的Calculator
模块很相似,但却没有改变原模块里的对象。 下面是测试ProgrammerCalculator类的代码:
TestProgrammerCalculator.ts
import { Calculator, test } from "./ProgrammerCalculator"; let c = new Calculator(2);
test(c, "001+010="); // prints 3
模块里不要使用命名空间
System.Collections
里找到所有集合的类型。 通过将类型有层次地组织在命名空间里,可以方便用户找到与使用那些类型。 然而,模块本身已经存在于文件系统之中,这是必须的。 我们必须通过路径和文件名找到它们,这已经提供了一种逻辑上的组织形式。 我们可以创建/collections/generic/
文件夹,把相应模块放在这里面。
My.Application.Customer.AddForm
和My.Application.Order.AddForm
-- 两个类型的名字相同,但命名空间不同。 然而,这对于模块来说却不是一个问题。 在一个模块里,没有理由两个对象拥有同一个名字。 从模块的使用角度来说,使用者会挑出他们用来引用模块的名字,所以也没有理由发生重名的情况。
危险信号
export namespace Foo { ... }
(删除Foo
并把所有内容向上层移动一层)
export class
或export function
(考虑使用export default
)
export namespace Foo {
(不要以为这些会合并到一个Foo
中!)
[转]TypeScript中文文档——命名空间 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说
这篇文章描述了如何在TypeScript里使用命名空间(之前叫做“内部模块”)来组织你的代码。
就像我们在术语说明里提到的那样,“内部模块”现在叫做“命名空间”。
另外,任何使用
这就避免了让新的使用者被相似的名称所迷惑。
我们先来写一段程序并将在整篇文章中都使用这个例子。 我们定义几个简单的字符串验证器,假设你会使用它们来验证表单里的用户输入或验证外部数据。
随着更多验证器的加入,我们需要一种手段来组织代码,以便于在记录它们类型的同时还不用担心与其它对象产生命名冲突。 因此,我们把验证器包裹到一个命名空间内,而不是把它们放在全局命名空间下。
下面的例子里,把所有与验证器相关的类型都放到一个叫做
当应用变得越来越大时,我们需要将代码分离到不同的文件中以便于维护。
现在,我们把
当涉及到多文件时,我们必须确保所有编译后的代码都被加载了。 我们有两种方式。
第一种方式,把所有的输入文件编译为一个输出文件,需要使用
编译器会根据源码里的引用标签自动地对输出进行排序。你也可以单独地指定每个文件。
第二种方式,我们可以编译每一个文件(默认方式),那么每个源文件都会对应生成一个JavaScript文件。 然后,在页面上通过
另一种简化命名空间操作的方法是使用
注意,我们并没有使用
为了描述不是用TypeScript编写的类库的类型,我们需要声明类库导出的API。 由于大部分程序库只提供少数的顶级对象,命名空间是用来表示它们的一个好办法。
我们称其为声明是因为它不是外部程序的具体实现。 我们通常在
流行的程序库D3在全局对象
module X {
相当于现在推荐的写法 namespace X {
)。
介绍
module
关键字来声明一个内部模块的地方都应该使用namespace
关键字来替换。
第一步
所有的验证器都放在一个文件里
interface StringValidator {
isAcceptable(s: string): boolean;
} let lettersRegexp = /^[A-Za-z]+$/; let numberRegexp = /^[0-9]+$/; class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) { return lettersRegexp.test(s);
}
} class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
} // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator(); // Show whether each string passed each validator strings.forEach(s => { for (let name in validators) { console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
}
});
命名空间
Validation
的命名空间里。 因为我们想让这些接口和类在命名空间之外也是可访问的,所以需要使用export
。 相反的,变量lettersRegexp
和numberRegexp
是实现的细节,不需要导出,因此它们在命名空间外是不能访问的。 在文件末尾的测试代码里,由于是在命名空间之外访问,因此需要限定类型的名称,比如Validation.LettersOnlyValidator
。
使用命名空间的验证器
namespace Validation { export interface StringValidator {
isAcceptable(s: string): boolean;
} const lettersRegexp = /^[A-Za-z]+$/; const numberRegexp = /^[0-9]+$/; export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) { return lettersRegexp.test(s);
}
} export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
}
} // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator strings.forEach(s => { for (let name in validators) { console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
});
分离到多文件
多文件中的命名空间
Validation
命名空间分割成多个文件。 尽管是不同的文件,它们仍是同一个命名空间,并且在使用的时候就如同它们在一个文件中定义的一样。 因为不同文件之间存在依赖关系,所以我们加入了引用标签来告诉编译器文件之间的关联。 我们的测试代码保持不变。
Validation.ts
namespace Validation { export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
LettersOnlyValidator.ts
/// <reference path="Validation.ts" /> namespace Validation { const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) { return lettersRegexp.test(s);
}
}
}
ZipCodeValidator.ts
/// <reference path="Validation.ts" /> namespace Validation { const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
}
}
}
Test.ts
/// <reference path="Validation.ts" /> /// <reference path="LettersOnlyValidator.ts" /> /// <reference path="ZipCodeValidator.ts" /> // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator strings.forEach(s => { for (let name in validators) { console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
}
});
--outFile
标记:
tsc --outFile sample.js Test.ts
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
<script>
标签把所有生成的JavaScript文件按正确的顺序引进来,比如:
MyTestPage.html (excerpt)
<script src="Validation.js" type="text/javascript" />
<script src="LettersOnlyValidator.js" type="text/javascript" />
<script src="ZipCodeValidator.js" type="text/javascript" />
<script src="Test.js" type="text/javascript" />
别名
import q = x.y.z
给常用的对象起一个短的名字。 不要与用来加载模块的import x = require('name')
语法弄混了,这里的语法是为指定的符号创建一个别名。 你可以用这种方法为任意标识符创建别名,也包括导入的模块中的对象。
namespace Shapes { export namespace Polygons { export class Triangle { } export class Square { }
}
} import polygons = Shapes.Polygons; let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"
require
关键字,而是直接使用导入符号的限定名赋值。 这与使用var
相似,但它还适用于类型和导入的具有命名空间含义的符号。 重要的是,对于值来讲,import
会生成与原始符号不同的引用,所以改变别名的var
值并不会影响原始变量的值。
使用其它的JavaScript库
.d.ts
里写这些声明。 如果你熟悉C/C++,你可以把它们当做.h
文件。 让我们看一些例子。
外部命名空间
d3
里定义它的功能。 因为这个库通过一个<script>
标签加载(不是通过模块加载器),它的声明文件使用内部模块来定义它的类型。 为了让TypeScript编译器识别它的类型,我们使用外部命名空间声明。 比如,我们可以像下面这样写:
D3.d.ts (部分摘录)
declare namespace D3 { export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
} export interface Event {
x: number;
y: number;
} export interface Base extends Selectors {
event: Event;
}
} declare let d3: D3.Base;
[转]TypeScript中文文档——命名空间和模块 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说
这篇文章将概括介绍在TypeScript里使用模块与命名空间来组织代码的方法。 我们也会谈及命名空间和模块的高级使用场景,和在使用它们的过程中常见的陷阱。
查看模块章节了解关于模块的更多信息。 查看命名空间章节了解关于命名空间的更多信息。
命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象。 这令命名空间十分容易使用。 它们可以在多文件中同时使用,并通过
但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中。
像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖。
模块会把依赖添加到模块加载器上(例如CommonJs / Require.js)。 对于小型的JS应用来说可能没必要,但是对于大型应用,这一点点的花费会带来长久的模块化和可维护性上的便利。 模块也提供了更好的代码重用,更强的封闭性以及更好的使用工具进行优化。
对于Node.js应用来说,模块是默认并推荐的组织代码的方式。
从ECMAScript 2015开始,模块成为了语言内置的部分,应该会被所有正常的解释引擎所支持。 因此,对于新项目来说推荐使用模块做为组织代码的方式。
这部分我们会描述常见的命名空间和模块的使用陷阱和如何去避免它们。
一个常见的错误是使用
编译器首先尝试去查找相应路径下的
这里的引用标签指定了外来模块的位置。 这就是一些Typescript例子中引用
如果你想把命名空间转换为模块,它可能会像下面这个文件一件:
顶层的模块
TypeScript里模块的一个特点是不同的模块永远也不会在相同的作用域内使用相同的名字。 因为使用模块的人会为它们命名,所以完全没有必要把导出的符号包裹在一个命名空间里。
再次重申,不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突。 模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。
下面是改进的例子:
就像每个JS文件对应一个模块一样,TypeScript里模块文件与生成的JS文件也是一一对应的。 这会产生一种影响,根据你指定的目标模块系统的不同,你可能无法连接多个模块源文件。 例如当目标模块系统为
module X {
相当于现在推荐的写法 namespace X {
)。
介绍
使用命名空间
--outFile
结合在一起。 命名空间是帮你组织Web应用不错的方式,你可以把所有依赖都放在HTML页面的<script>
标签里。
使用模块
命名空间和模块的陷阱
对模块使用
/// <reference>
/// <reference>
引用模块文件,应该使用import
。 要理解这之间的区别,我们首先应该弄清编译器是如何根据import
路径(例如,import x from "...";
或import x = require("...")
里面的...
,等等)来定位模块的类型信息的。
.ts
,.tsx
再或者.d.ts
。 如果这些文件都找不到,编译器会查找外部模块声明。 回想一下,它们是在.d.ts
文件里声明的。
myModules.d.ts
// In a .d.ts file or .ts file that is not a module: declare module "SomeModule" { export function fn(): string;
}
myOtherModule.ts
/// <reference path="myModules.d.ts" /> import * as m from "SomeModule";
node.d.ts
的方法。
不必要的命名空间
shapes.ts
export namespace Shapes { export class Triangle { /* ... */ } export class Square { /* ... */ }
}
Shapes
包裹了Triangle
和Square
。 对于使用它的人来说这是令人迷惑和讨厌的:
shapeConsumer.ts
import * as shapes from "./shapes"; let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
shapes.ts
export class Triangle { /* ... */ } export class Square { /* ... */ }
shapeConsumer.ts
import * as shapes from "./shapes"; let t = new shapes.Triangle();
模块的取舍
commonjs
或umd
时,无法使用outFile
选项,但是在TypeScript 1.8以上的版本能够使用outFile
当目标为amd
或system
。
[转]TypeScript中文文档——模块解析 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
这节假设你已经了解了模块的一些基本知识 请阅读模块文档了解更多信息。
模块解析就是指编译器所要依据的一个流程,用它来找出某个导入操作所引用的具体值。 假设有一个导入语句
这时候,编译器会想知道“
首先,编译器会尝试定位表示导入模块的文件。 编译会遵循下列二种策略之一:Classic或Node。 这些策略会告诉编译器到哪里去查找
如果它们失败了并且如果模块名是非相对的(且是在
最后,如果编译器还是不能解析这个模块,它会记录一个错误。 在这种情况下,错误可能为
根据模块引用是相对的还是非相对的,模块导入会以不同的方式解析。
相对导入是以
所有其它形式的导入被当作非相对的。 下面是一些例子:
相对导入解析时是相对于导入它的文件来的,并且不能解析为一个外部模块声明。 你应该为你自己写的模块使用相对导入,这样能确保它们在运行时的相对位置。
共有两种可用的模块解析策略:Node和Classic。 你可以使用
这种策略以前是TypeScript默认的解析策略。 现在,它存在的理由主要是为了向后兼容。
相对导入的模块是相对于导入它的文件进行解析的。 因此
对于非相对模块的导入,编译器则会从包含导入文件的目录开始依次向上级目录遍历,尝试定位匹配的声明文件。
比如:
有一个对
这个解析策略试图在运行时模仿Node.js模块解析机制。 完整的Node.js解析算法可以在Node.js module documentation找到。
为了理解TypeScript编译依照的解析步骤,先弄明白Node.js模块是非常重要的。 通常,在Node.js里导入是通过
相对路径很简单。 例如,假设有一个文件路径为
将
将
将
你可以阅读Node.js文档了解更多详细信息:file modules 和 folder modules。
但是,非相对模块名的解析是个完全不同的过程。 Node会在一个特殊的文件夹
还是用上面例子,但假设
注意Node.js在步骤(4)和(7)会向上跳一级目录。
你可以阅读Node.js文档了解更多详细信息:loading modules from
TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件。 因此,TypeScript在Node解析逻辑基础上增加了TypeScript源文件的扩展名(
比如,有一个导入语句
回想一下Node.js先查找
类似地,非相对的导入会遵循Node.js的解析逻辑,首先查找文件,然后是合适的文件夹。 因此
不要被这里步骤的数量吓到 - TypeScript只是在步骤(8)和(15)向上跳了两次目录。 这并不比Node.js里的流程复杂。
正常来讲编译器会在开始编译之前解析模块导入。 每当它成功地解析了对一个文件
比如
使用
有些是被
因此,要从编译列表中排除一个文件,你需要在排除它的同时,还要排除所有对它进行
import { a } from "moduleA"
; 为了去检查任何对a
的使用,编译器需要准确的知道它表示什么,并且会需要检查它的定义moduleA
。
moduleA
的shape是怎样的?” 这听上去很简单,moduleA
可能在你写的某个.ts
/.tsx
文件里或者在你的代码所依赖的.d.ts
里。
moduleA
。
"moduleA"
的情况下),编译器会尝试定位一个外部模块声明。 我们接下来会讲到非相对导入。
error TS2307: Cannot find module 'moduleA'.
相对 vs. 非相对模块导入
/
,./
或../
开头的。 下面是一些例子:
import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";
import * as $ from "jQuery";
import { Component } from "angular2/core";
模块解析策略
--moduleResolution
标记为指定使用哪个。 默认值为Node。
Classic
/root/src/folder/A.ts
文件里的import { b } from "./moduleB"
会使用下面的查找流程:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
moduleB
的非相对导入import { b } from "moduleB"
,它是在/root/src/folder/A.ts
文件里,会以如下的方式来定位"moduleB"
:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
Node
Node.js如何解析模块
require
函数调用进行的。 Node.js会根据require
的是相对路径还是非相对路径做出不同的行为。
/root/src/moduleA.js
,包含了一个导入var x = require("./moduleB");
Node.js以下面的顺序解析这个导入:
/root/src/moduleB.js
视为文件,检查是否存在。
/root/src/moduleB
视为目录,检查是否它包含package.json
文件并且其指定了一个"main"
模块。 在我们的例子里,如果Node.js发现文件/root/src/moduleB/package.json
包含了{ "main": "lib/mainModule.js" }
,那么Node.js会引用/root/src/moduleB/lib/mainModule.js
。
/root/src/moduleB
视为目录,检查它是否包含index.js
文件。 这个文件会被隐式地当作那个文件夹下的"main"模块。
node_modules
里查找你的模块。 node_modules
可能与当前文件在同一级目录下,或者在上层目录里。 Node会向上级目录遍历,查找每个node_modules
直到它找到要加载的模块。
/root/src/moduleA.js
里使用的是非相对路径导入var x = require("moduleB");
。 Node则会以下面的顺序去解析moduleB
,直到有一个匹配上。
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
(如果指定了"main"
属性)
/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
(如果指定了"main"
属性)
/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
(如果指定了"main"
属性)
/node_modules/moduleB/index.js
node_modules
。
TypeScript如何解析模块
.ts
,.tsx
和.d.ts
)。 同时,TypeScript在package.json
里使用字段"typings"
来表示类似"main"
的意义 - 编译器会使用它来找到要使用的"main"定义文件。
import { b } from "./moduleB"
在/root/src/moduleA.ts
里,会以下面的流程来定位"./moduleB"
:
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
(如果指定了"typings"
属性)
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
moduleB.js
文件,然后是合适的package.json
,再之后是index.js
。
/src/moduleA.ts
文件里的import { b } from "moduleB"
会以下面的查找顺序解析:
/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json
(如果指定了"typings"
属性)
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts
/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json
(如果指定了"typings"
属性)
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts
/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json
(如果指定了"typings"
属性)
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
使用
--noResolve
import
,这个文件被会加到一个文件列表里,以供编译器稍后处理。
--noResolve
编译选项告诉编译器不要添加任何不是在命令行上传入的文件到编译列表。 编译器仍然会尝试解析模块,但是只要没有指定这个文件,那么它就不会被包含在内。
app.ts
import * as A from "moduleA" // OK, moduleA passed on the command-line import * as B from "moduleB" // Error TS2307: Cannot find module 'moduleB'.
tsc app.ts moduleA.ts --noResolve
--noResolve
编译app.ts
:
moduleA
,因为它在命令行上指定了。
moduleB
,因为没有在命令行上传递。
常见问题
为什么在
exclude
列表里的模块还会被编译器使用
tsconfig.json
将文件夹转变一个“工程” 如果不指定任何“exclude”
或“files”
,文件夹里的所有文件包括tsconfig.json
和所有的子目录都会在编译列表里。 如果你想利用“exclude”
排除某些文件,甚至你想指定所有要编译的文件列表,请使用“files”
。
tsconfig.json
自动加入的。 它不会涉及到上面讨论的模块解析。 如果编译器识别出一个文件是模块导入目标,它就会加到编译列表里,不管它是否被排除了。
import
或使用了/// <reference path="..." />
指令的文件。
[转]TypeScript中文文档——合并申明 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
TypeScript中有些独特的概念可以在类型层面上描述JavaScript对象的模型。 这其中尤其独特的一个例子是“声明合并”的概念。 理解了这个概念,将有助于操作现有的JavaScript代码。 同时,也会有助于理解更多高级抽象的概念。
对本文件来讲,“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并;不局限于两个声明。
Typescript中的声明会创建以下三种实体之一:命名空间,类型或值。 创建命名空间的声明会新建一个命名空间,它包含了用(.)符号来访问时使用的名字。 创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上。 最后,创建值的声明会创建在JavaScript输出中看到的值。
理解每个声明创建了什么,有助于理解当声明合并时有哪些东西被合并了。
最简单也最常见的声明合并类型是接口合并。 从根本上说,合并的机制是把双方的成员放到一个同名的接口里。
接口的非函数的成员必须是唯一的。 如果两个接口中同时声明了同名的非函数成员编译器则会报错。
对于函数成员,每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意,当接口
如下例所示:
这三个接口合并成一个声明:
注意每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。
这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。
比如,下面的接口会合并到一起:
合并后的
与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。
对于命名空间的合并,模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。
对于命名空间里值的合并,如果当前已经存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块里。
等同于:
除了这些合并外,你还需要了解非导出成员是如何处理的。 非导出成员仅在其原有的(合并前的)命名空间内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。
下例提供了更清晰的说明:
因为
命名空间可以与其它类型的声明进行合并。 只要命名空间的定义符合将要合并类型的定义。合并结果包含两者的声明类型。 Typescript使用这个功能去实现一些JavaScript里的设计模式。
这让我们可以表示内部类。
合并规则与上面
除了内部类的模式,你在JavaScript里,创建一个函数稍后扩展它增加一些属性也是很常见的。 Typescript使用声明合并来达到这个目的并保证类型安全。
相似的,命名空间可以用来扩展枚举型:
TypeScript并非允许所有的合并。 目前,类不能与其它类或变量合并。 想要了解如何模仿类的合并,请参考TypeScript的混入。
虽然JavaScript不支持合并,但你可以为导入的对象打补丁以更新它们。让我们考察一下这个玩具性的示例:
它也可以很好地工作在TypeScript中, 但编译器对
模块名的解析和用
你也以在模块内部添加声明到全局作用域中。
全局扩展与模块扩展的行为和限制是相同的。
介绍
基础概念
Declaration Type
Namespace
Type
Value
Namespace
X
X
Class
X
X
Enum
X
X
Interface
X
Type Alias
X
Function
X
Variable
X
合并接口
interface Box {
height: number;
width: number;
} interface Box {
scale: number;
} let box: Box = {height: 5, width: 6, scale: 10};
A
与后来的接口A
合并时,后面的接口具有更高的优先级。
interface Cloner {
clone(animal: Animal): Animal;
} interface Cloner {
clone(animal: Sheep): Sheep;
} interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
interface Document {
createElement(tagName: any): Element;
} interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
} interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
Document
将会像下面这样:
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
合并命名空间
Animals
声明合并示例:
namespace Animals { export class Zebra { }
}
namespace Animals { export interface Legged { numberOfLegs: number; } export class Dog { }
}
namespace Animals { export interface Legged { numberOfLegs: number; } export class Zebra { } export class Dog { }
}
namespace Animal { let haveMuscles = true; export function animalsHaveMuscles() { return haveMuscles;
}
}
namespace Animal { export function doAnimalsHaveMuscles() { return haveMuscles; // <-- error, haveMuscles is not visible here }
}
haveMuscles
并没有导出,只有animalsHaveMuscles
函数共享了原始未合并的命名空间可以访问这个变量。 doAnimalsHaveMuscles
函数虽是合并命名空间的一部分,但是访问不了未导出的成员。
命名空间与类和函数和枚举类型合并
合并命名空间和类
class Album {
label: Album.AlbumLabel;
}
namespace Album { export class AlbumLabel { }
}
合并命名空间
小节里讲的规则一致,我们必须导出AlbumLabel
类,好让合并的类能访问。 合并结果是一个类并带有一个内部类。 你也可以使用命名空间为类增加一些静态属性。
function buildLabel(name: string): string { return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel { export let suffix = ""; export let prefix = "Hello, ";
}
alert(buildLabel("Sam Smith"));
enum Color {
red = 1,
green = 2,
blue = 4 }
namespace Color { export function mixColor(colorName: string) { if (colorName == "yellow") { return Color.red + Color.green;
} else if (colorName == "white") { return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") { return Color.red + Color.blue;
} else if (colorName == "cyan") { return Color.green + Color.blue;
}
}
}
非法的合并
模块扩展
// observable.js export class Observable<T> { // ... implementation left as an exercise for the reader ... } // map.js import { Observable } from "./observable";
Observable.prototype.map = function (f) { // ... another exercise for the reader }
Observable.prototype.map
一无所知。 你可以使用扩展模块来将它告诉编译器:
// observable.ts stays the same // map.ts import { Observable } from "./observable"; declare module "./observable" { interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) { // ... another exercise for the reader } // consumer.ts import { Observable } from "./observable"; import "./map"; let o: Observable<number>;
o.map(x => x.toFixed());
import
/export
解析模块标识符的方式是一致的。 更多信息请参考 Modules。 当这些声明在扩展中合并时,就好像在原始位置被声明了一样。但是,你不能在扩展中声明新的顶级声明--仅可以扩展模块中已经存在的声明。
全局扩展
// observable.ts export class Observable<T> { // ... still no implementation ... } declare global { interface Array<T> {
toObservable(): Observable<T>;
}
} Array.prototype.toObservable = function () { // ... }
[转]TypeScript中文文档——Symbol TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
自ECMAScript 2015起,
Symbols是不可改变且唯一的。
像字符串一样,symbols也可以被用做对象属性的键。
Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。
除了用户定义的symbols,还有一些已经众所周知的内置symbols。 内置symbols用来表示语言内部的行为。
以下为这些symbols的列表:
方法,会被
布尔值,表示当在一个对象上调用
方法,被
方法,被
方法,被
方法,被
函数值,为一个构造函数。用来创建派生对象。
方法,被
方法,被
方法,被内置方法
对象,它自己拥有的属性会被
介绍
symbol
成为了一种新的原生类型,就像number
和string
一样。
symbol
类型的值是通过Symbol
构造函数创建的。
let sym1 = Symbol(); let sym2 = Symbol("key"); // 可选的字符串key
let sym2 = Symbol("key"); let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
let sym = Symbol(); let obj = {
[sym]: "value" }; console.log(obj[sym]); // "value"
const getClassNameSymbol = Symbol(); class C {
[getClassNameSymbol](){ return "C";
}
} let c = new C(); let className = c[getClassNameSymbol](); // "C"
众所周知的Symbols
Symbol.hasInstance
instanceof
运算符调用。构造器对象用来识别一个对象是否是其实例。
Symbol.isConcatSpreadable
Array.prototype.concat
时,这个对象的数组元素是否可展开。
Symbol.iterator
for-of
语句调用。返回对象的默认迭代器。
Symbol.match
String.prototype.match
调用。正则表达式用来匹配字符串。
Symbol.replace
String.prototype.replace
调用。正则表达式用来替换字符串中匹配的子串。
Symbol.search
String.prototype.search
调用。正则表达式返回被匹配部分在字符串中的索引。
Symbol.species
Symbol.split
String.prototype.split
调用。正则表达式来用分割字符串。
Symbol.toPrimitive
ToPrimitive
抽象操作调用。把对象转换为相应的原始值。
Symbol.toStringTag
Object.prototype.toString
调用。返回创建对象时默认的字符串描述。
Symbol.unscopables
with
作用域排除在外。