[转]TypeScript中文文档——枚举 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
使用枚举我们可以定义一些有名字的数字常量。 枚举通过
一个枚举类型可以包含零个或多个枚举成员。 枚举成员具有一个数字值,它可以是常数或是计算得出的值当满足如下条件时,枚举成员被当作是常数:
所有其它情况的枚举成员被当作是需要计算得出的值。
枚举是在运行时真正存在的一个对象。 其中一个原因是因为这样可以从枚举值到枚举名进行反向映射。
编译成:
生成的代码中,枚举类型被编译成一个对象,它包含双向映射(
常数枚举只能使用常数枚举表达式并且不同于常规的枚举的是它们在编译阶段会被删除。 常数枚举成员在使用的地方被内联进来。 这是因为常数枚举不可能有计算成员。
生成后的代码为:
外部枚举用来描述已经存在的枚举类型的形状。
外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。
枚举
enum
关键字来定义。
enum Direction {
Up = 1,
Down,
Left,
Right
}
0
。
+
, -
, ~
一元运算符应用于常数枚举表达式
+
, -
, *
, /
, %
, <<
, >>
, >>>
, &
, |
, ^
二元运算符,常数枚举表达式做为其一个操作对象 若常数枚举表达式求值后为NaN
或Infinity
,则会在编译阶段报错。
enum FileAccess { // constant members None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write // computed member G = "123".length
}
enum Enum {
A
} let a = Enum.A; let nameOfA = Enum[Enum.A]; // "A"
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {})); var a = Enum.A; var nameOfA = Enum[Enum.A]; // "A"
name
-> value
)和(value
-> name
)。 引用枚举成员总会生成一次属性访问并且永远不会内联。 在大多数情况下这是很好的并且正确的解决方案。 然而有时候需求却比较严格。 当访问枚举值时,为了避免生成多余的代码和间接引用,可以使用常数枚举。 常数枚举是在enum
关键字前使用const
修饰符。
const enum Enum {
A = 1,
B = A * 2 }
const enum Directions {
Up,
Down,
Left,
Right
} let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
外部枚举
declare enum Enum {
A = 1,
B,
C = 2 }
[转]TypeScript中文文档——泛型 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
介绍
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型
来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
泛型之Hello World
下面来创建第一个使用泛型的例子:identity函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是echo
命令。
不用泛型的话,这个函数可能是下面这样:
function identity(arg: number): number { return arg;
}
或者,我们使用any
类型来定义函数:
function identity(arg: any): any { return arg;
}
虽然使用any
类型后这个函数已经能接收任何类型的arg参数,但是却丢失了一些信息:传入的类型与返回的类型应该是相同的。 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T { return arg;
}
我们给identity添加了类型变量T
。 T
帮助我们捕获用户传入的类型(比如:number
),之后我们就可以使用这个类型。 之后我们再次使用了T
当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的identity
函数叫做泛型,因为它可以适用于多个类型。 不同于使用any
,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。
我们定义了泛型函数后,可以用两种方法使用。 第一种是,传入所有的参数,包含类型参数:
let output = identity<string>("myString"); // type of output will be 'string'
这里我们明确的指定了T
是string
类型,并做为一个参数传给函数,使用了<>
括起来而不是()
。
第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let output = identity("myString"); // type of output will be 'string'
注意我们没必要使用尖括号(<>
)来明确地传入类型;编译器可以查看myString
的值,然后把T
设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
使用泛型变量
使用泛型创建像identity
这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当做是任意或所有类型。
看下之前identity
例子:
function identity<T>(arg: T): T { return arg;
}
如果我们想同时打印出arg
的长度。 我们很可能会这样做:
function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg;
}
如果这么做,编译器会报错说我们使用了arg
的.length
属性,但是没有地方指明arg
具有这个属性。 记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有.length
属性的。
现在假设我们想操作T
类型的数组而不直接是T
。由于我们操作的是数组,所以.length
属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以这样理解loggingIdentity
的类型:泛型函数loggingIdentity
,接收类型参数T
,和函数arg
,它是个元素类型是T
的数组,并返回元素类型是T
的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时T
的的类型为number
。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。
我们也可以这样实现上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> { console.log(arg.length); // Array has a .length, so no more error return arg;
}
使用过其它语言的话,你可能对这种语法已经很熟悉了。 在下一节,会介绍如何创建自定义泛型像Array<T>
一样。
泛型类型
上一节,我们创建了identity通用函数,可以适用于不同的类型。 在这节,我们研究一下函数本身的类型,以及如何创建泛型接口。
泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
function identity<T>(arg: T): T { return arg;
} let myIdentity: <T>(arg: T) => T = identity;
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
function identity<T>(arg: T): T { return arg;
} let myIdentity: <U>(arg: U) => U = identity;
我们还可以使用带有调用签名的对象字面量来定义泛型函数:
function identity<T>(arg: T): T { return arg;
} let myIdentity: {<T>(arg: T): T} = identity;
这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:
interface GenericIdentityFn {
<T>(arg: T): T;
} function identity<T>(arg: T): T { return arg;
} let myIdentity: GenericIdentityFn = identity;
一个相似的例子,我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如:Dictionary<string>而不只是Dictionary
)。 这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
(arg: T): T;
} function identity<T>(arg: T): T { return arg;
} let myIdentity: GenericIdentityFn<number> = identity;
注意,我们的示例做了少许改动。 不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。 当我们使用GenericIdentityFn
的时候,还得传入一个类型参数来指定泛型类型(这里是:number
),锁定了之后代码里使用的类型。 对于描述哪部分类型属于泛型部分来说,理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。
除了泛型接口,我们还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间。
泛型类
泛型类看上去与泛型接口差不多。 泛型类使用(<>
)括起泛型类型,跟在类名后面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
} let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
GenericNumber
类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number
类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
alert(stringNumeric.add(stringNumeric.zeroValue, "test"));
与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
泛型约束
你应该会记得之前的一个例子,我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在loggingIdentity
例子中,我们想访问arg
的length
属性,但是编译器并不能证明每种类型都有length
属性,所以就报错了。
function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg;
}
相比于操作any所有类型,我们想要限制函数去处理任意带有.length
属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含.length
属性的接口,使用这个接口和extends
关键字还实现约束:
interface Lengthwise {
length: number;
} function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
在泛型约束中使用类型参数
你可以声明一个类型参数,且它被另一个类型参数所约束。比如,
function find<T, U extends Findable<T>>(n: T, s: U) { // ... }
find (giraffe, myAnimals);
在泛型里使用类类型
在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,
function create<T>(c: {new(): T; }): T {
return new c();
}
一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function findKeeper<A extends Animal, K> (a: {new(): A;
prototype: {keeper: K}}): K {
return a.prototype.keeper;
}
findKeeper(Lion).nametag; // typechecks!
[转]TypeScript中文文档——函数 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。
和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。
通过下面的例子可以迅速回想起这两种JavaScript中的函数:
在JavaScript里,函数可以使用函数体外部的变量。 当函数这么做时,我们说它‘捕获’了这些变量。 至于为什么可以这样做以及其中的利弊超出了本文的范围,但是深刻理解这个机制对学习JavaScript和TypeScript会很有帮助。
让我们为上面那个函数添加类型:
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。
现在我们已经为函数指定了类型,下面让我们写出函数的完整类型。
函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。 我们以参数列表的形式写出参数类型,为每个参数指定一个名字和类型。 这个名字只是为了增加可读性。 我们也可以这么写:
只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。
第二部分是返回值类型。 对于返回值,我们在函数和返回值类型之前使用(
函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。
尝试这个例子的时候,你会发现如果你在赋值语句的一边指定了类型但是另一边没有类型的话,TypeScript编译器会自动识别出类型:
这叫做“按上下文归类”,是类型推论的一种。 它帮助我们更好地为程序指定类型。
TypeScript里的每个函数参数都是必须的。 这不是指不能传递
JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用
可选参数必须跟在必须参数后面。 如果上例我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面。
在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是
在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。 也就是说可选参数与末尾的默认参数共享参数类型。
和
共享同样的类型
与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用
在TypeScript里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号(
这个省略号也会在带有剩余参数的函数类型定义上使用到:
学习使用JavaScript里
JavaScript里,
下面看一个例子:
可以看到
为了解决这个问题,我们可以在函数被返回时就绑好正确的
我们把函数表达式变为使用lambda表达式( () => {} )。 这样就会在函数创建的时候就指定了‘this’值,而不是在函数调用的时候。
更好事情是,TypeScript会警告你犯了一个错误,如果你给编译器设置了
不幸的是,
让我们往例子里添加一些接口,
现在TypeScript知道
你可以也看到过在回调函数里的
指定了
因为
这是可行的因为箭头函数不会捕获
JavaScript本身是个动态语言。 JavaScript里函数根据传入不同的参数而返回不同类型的数据是很常见的。
方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 下面我们来重载
这样改变后,重载的
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
注意,
介绍
函数
// Named function function add(x, y) { return x + y;
} // Anonymous function let myAdd = function(x, y) { return x + y; };
let z = 100; function addToZ(x, y) { return x + y + z;
}
函数类型
为函数定义类型
function add(x: number, y: number): number { return x + y;
} let myAdd = function(x: number, y: number): number { return x+y; };
书写完整函数类型
let myAdd: (x:number, y:number)=>number = function(x: number, y: number): number { return x+y; };
let myAdd: (baseValue:number, increment:number) => number = function(x: number, y: number): number { return x + y; };
=>
)符号,使之清晰明了。 如之前提到的,返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为void
而不能留空。
推断类型
// myAdd has the full function type let myAdd = function(x: number, y: number): number { return x + y; }; // The parameters `x` and `y` have the type number let myAdd: (baseValue:number, increment:number) => number = function(x, y) { return x + y; };
可选参数和默认参数
null
或undefined
作为参数,而是说编译器检查用户是否为每个参数都传入了值。 编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
function buildName(firstName: string, lastName: string) { return firstName + " " + lastName;
} let result1 = buildName("Bob"); // error, too few parameters let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters let result3 = buildName("Bob", "Adams"); // ah, just right
?
实现可选参数的功能。 比如,我们想让last name是可选的:
function buildName(firstName: string, lastName?: string) { if (lastName) return firstName + " " + lastName; else return firstName;
} let result1 = buildName("Bob"); // works correctly now let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters let result3 = buildName("Bob", "Adams"); // ah, just right
undefined
时。 它们叫做有默认初始化值的参数。 让我们修改上例,把last name的默认值设置为"Smith"
。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right
function buildName(firstName: string, lastName?: string) { // ... }
function buildName(firstName: string, lastName = "Smith") {
// ...
}
(firstName: string, lastName?: string) => string
。 默认参数的默认值消失了,只保留了它是一个可选参数的信息。
undefined
值来获得默认值。 例如,我们重写最后一个例子,让firstName
是带默认值的参数:
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
剩余参数
arguments
来访问所有传入的参数。
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" ");
} let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
...
)后面给定的名字,你可以在函数体内使用这个数组。
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" ");
} let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
this
this
就好比一场成年礼。 由于TypeScript是JavaScript的超集,TypeScript程序员也需要弄清this
工作机制并且当有bug的时候能够找出错误所在。 幸运的是,TypeScript能通知你错误地使用了this
的地方。 如果你想了解JavaScript里的this
是如何工作的,那么首先阅读Yehuda Katz写的Understanding JavaScript Function Invocation and "this"。 Yehuda的文章详细的阐述了this
的内部工作原理,因此我们这里只做简单介绍。
this
和箭头函数
this
的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() { return function() { 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);
createCardPicker
是个函数,并且它又返回了一个函数。 如果我们尝试运行这个程序,会发现它并没有弹出对话框而是报错了。 因为createCardPicker
返回的函数里的this
被设置成了window
而不是deck
对象。 因为我们只是独立的调用了cardPicker()
。 顶级的非方法式调用会将this
视为window
。 (注意:在严格模式下,this
为undefined
而不是window
)。
this
。 这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 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);
--noImplicitThis
标记。 它会指出this.suits[pickedSuit]
里的this
的类型为any
。
this
参数
this.suits[pickedSuit]
的类型依旧为any
。 这是因为this
来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的this
参数。 this
参数是个假的参数,它出现在参数列表的最前面:
function f(this: void) { // make sure `this` is unusable in this standalone function }
Card
和 Deck
,让类型重用能够变得清晰简单些:
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);
createCardPicker
期望在某个Deck
对象上调用。 也就是说this
是Deck
类型的,而非any
,因此--noImplicitThis
不会报错了。
this
参数在回调函数里
this
报错,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用,this
将为undefined
。 稍做改动,你就可以通过this
参数来避免错误。 首先,库函数的作者要指定this
的类型:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this: void
means that addClickListener
expects onclick
to be a function that does not require a this
type. Second, annotate your calling code with this
:
class Handler {
info: string;
onClickBad(this: Handler, e: Event) { // oops, used this here. using this callback would crash at runtime this.info = e.message;
};
} let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
this
类型后,你显式声明onClickBad
必须在Handler
的实例上调用。 然后TypeScript会检测到addClickListener
要求函数带有this: void
。 改变this
类型来修复这个错误:
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);
onClickGood
指定了this
类型为void
,因此传递addClickListener
是合法的。 当然了,这也意味着不能使用this.info
. 如果你两者都想要,你不得不使用箭头函数了:
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
this
,所以你总是可以把它们传给期望this: void
的函数。 缺点是每个Handler
对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到Handler
的原型链上。 它们在不同Handler
对象间是共享的。
重载
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x): any { // Check to see if we're working with an object/array // if so, they gave us the deck and we'll pick the card if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard;
} // Otherwise just let them pick the card else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 };
}
} let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
pickCard
方法根据传入参数的不同会返回两种不同的类型。 如果传入的是代表纸牌的对象,函数作用是从中抓一张牌。 如果用户想抓牌,我们告诉他抓到了什么牌。 但是这怎么在类型系统里表示呢。
pickCard
函数。
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x: {suit: string; card: number; }[]): number; function pickCard(x: number): {suit: string; card: number; }; function pickCard(x): any { // Check to see if we're working with an object/array // if so, they gave us the deck and we'll pick the card if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard;
} // Otherwise just let them pick the card else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 };
}
} let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
pickCard
函数在调用的时候会进行正确的类型检查。
function pickCard(x): any
并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用pickCard
会产生错误。
[转]TypeScript中文文档——类 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
介绍
传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本。
类
下面看一个使用类的例子:
class Greeter {
greeting: string; constructor(message: string) { this.greeting = message;
}
greet() { return "Hello, " + this.greeting;
}
} let greeter = new Greeter("world");
如果你使用过C#或Java,你会对这种语法非常熟悉。 我们声明一个Greeter
类。这个类有3个成员:一个叫做greeting
的属性,一个构造函数和一个greet
方法。
你会注意到,我们在引用任何一个类成员的时候都用了this
。 它表示我们访问的是类的成员。
最后一行,我们使用new
构造了Greeter
类的一个实例。 它会调用之前定义的构造函数,创建一个Greeter
类型的新对象,并执行构造函数初始化它。
继承
在TypeScript里,我们可以使用常用的面向对象模式。 当然,基于类的程序设计中最基本的模式是允许使用继承来扩展现有的类。
看下面的例子:
class Animal {
name:string; constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`);
}
} class Snake extends Animal { constructor(name: string) { super(name); }
move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters);
}
} class Horse extends Animal { constructor(name: string) { super(name); }
move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters);
}
} let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
这个例子展示了TypeScript中继承的一些特征,它们与其它语言类似。 我们使用extends
关键字来创建子类。你可以看到Horse
和Snake
类是基类Animal
的子类,并且可以访问其属性和方法。
包含构造函数的派生类必须调用super()
,它会执行基类的构造方法。
这个例子演示了如何在子类里可以重写父类的方法。 Snake
类和Horse
类都创建了move
方法,它们重写了从Animal
继承来的move
方法,使得move
方法根据不同的类而具有不同的功能。 注意,即使tom
被声明为Animal
类型,但因为它的值是Horse
,tom.move(34)
会调用Horse
里的重写方法:
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
公共,私有与受保护的修饰符
默认为public
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用public
来做修饰;例如,C#要求必须明确地使用public
指定成员是可见的。 在TypeScript里,成员都默认为public
。
你也可以明确的将一个成员标记成public
。 我们可以用下面的方式来重写上面的Animal
类:
class Animal { public name: string; public constructor(theName: string) { this.name = theName; } public move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
理解private
当成员被标记成private
时,它就不能在声明它的类的外部访问。比如:
class Animal { private name: string; constructor(theName: string) { this.name = theName; }
} new Animal("Cat").name; // Error: 'name' is private;
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
然而,当我们比较带有private
或protected
成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个private
成员,那么只有当另外一个类型中也存在这样一个private
成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于protected
成员也使用这个规则。
下面来看一个例子,更好地说明了这一点:
class Animal { private name: string; constructor(theName: string) { this.name = theName; }
} class Rhino extends Animal { constructor() { super("Rhino"); }
} class Employee { private name: string; constructor(theName: string) { this.name = theName; }
} let animal = new Animal("Goat"); let rhino = new Rhino(); let employee = new Employee("Bob");
animal = rhino;
animal = employee; // Error: Animal and Employee are not compatible
这个例子中有Animal
和Rhino
两个类,Rhino
是Animal
类的子类。 还有一个Employee
类,其类型看上去与Animal
是相同的。 我们创建了几个这些类的实例,并相互赋值来看看会发生什么。 因为Animal
和Rhino
共享了来自Animal
里的私有成员定义private name: string
,因此它们是兼容的。 然而Employee
却不是这样。当把Employee
赋值给Animal
的时候,得到一个错误,说它们的类型不兼容。 尽管Employee
里也有一个私有成员name
,但它明显不是Animal
里面定义的那个。
理解protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问。例如:
class Person { protected name: string; constructor(name: string) { this.name = name; }
} class Employee extends Person { private department: string; constructor(name: string, department: string) { super(name) this.department = department;
} public getElevatorPitch() { return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
} let howard = new Employee("Howard", "Sales"); console.log(howard.getElevatorPitch()); console.log(howard.name); // error
注意,我们不能在Person
类外使用name
,但是我们仍然可以通过Employee
类的实例方法访问,因为Employee
是由Person
派生而来的。
构造函数也可以被标记成protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Person { protected name: string; protected constructor(theName: string) { this.name = theName; }
} // Employee can extend Person class Employee extends Person { private department: string; constructor(name: string, department: string) { super(name); this.department = department;
} public getElevatorPitch() { return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
} let howard = new Employee("Howard", "Sales"); let john = new Person("John"); // Error: The 'Person' constructor is protected
readonly修饰符
你可以使用readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8; constructor (theName: string) { this.name = theName;
}
} let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.
参数属性
在上面的例子中,我们不得不定义一个受保护的成员name
和一个构造函数参数theName
在Person
类里,并且立刻给name
和theName
赋值。 这种情况经常会遇到。参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前Animal
类的修改版,使用了参数属性:
class Animal { constructor(private name: string) { }
move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
注意看我们是如何舍弃了theName
,仅在构造函数里使用private name: string
参数来创建和初始化name
成员。 我们把声明和赋值合并至一处。
参数属性通过给构造函数参数添加一个访问限定符来声明。 使用private
限定一个参数属性会声明并初始化一个私有成员;对于public
和protected
来说也是一样。
存取器
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用get
和set
。 首先,我们从一个没有使用存取器的例子开始。
class Employee {
fullName: string;
} let employee = new Employee();
employee.fullName = "Bob Smith"; if (employee.fullName) { console.log(employee.fullName);
}
我们可以随意的设置fullName
,这是非常方便的,但是这也可能会带来麻烦。
下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对fullName
的直接访问改成了可以检查密码的set
方法。 我们也加了一个get
方法,让上面的例子仍然可以工作。
let passcode = "secret passcode"; class Employee { private _fullName: string; get fullName(): string { return this._fullName;
} set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName;
} else { console.log("Error: Unauthorized update of employee!");
}
}
} let employee = new Employee();
employee.fullName = "Bob Smith"; if (employee.fullName) {
alert(employee.fullName);
}
我们可以修改一下密码,来验证一下存取器是否是工作的。当密码不对时,会提示我们没有权限去修改员工。
对于存取器有下面几点需要注意的:
首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有get
不带有set
的存取器自动被推断为readonly
。 这在从代码生成.d.ts
文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用static
定义origin
,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在origin
前面加上类名。 如同在实例属性上使用this.
前缀来访问属性一样,这里我们使用Grid.
来访问静态属性。
class Grid { static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) { let xDist = (point.x - Grid.origin.x); let yDist = (point.y - Grid.origin.y); return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
} constructor (public scale: number) { }
} let grid1 = new Grid(1.0); // 1x scale let grid2 = new Grid(5.0); // 5x scale console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void { console.log('roaming the earch...');
}
}
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含abstract
关键字并且可以包含访问修饰符。
abstract class Department { constructor(public name: string) {
}
printName(): void { console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现 } class AccountingDepartment extends Department { constructor() { super('Accounting and Auditing'); // constructors in derived classes must call super() }
printMeeting(): void { console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void { console.log('Generating accounting reports...');
}
} let department: Department; // ok to create a reference to an abstract type department = new Department(); // error: cannot create an instance of an abstract class department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
高级技巧
构造函数
当你在TypeScript里声明了一个类的时候,实际上同时声明了很多东西。 首先就是类的实例的类型。
class Greeter {
greeting: string; constructor(message: string) { this.greeting = message;
}
greet() { return "Hello, " + this.greeting;
}
} let greeter: Greeter;
greeter = new Greeter("world"); console.log(greeter.greet());
这里,我们写了let greeter: Greeter
,意思是Greeter
类的实例的类型是Greeter
。 这对于用过其它面向对象语言的程序员来讲已经是老习惯了。
我们也创建了一个叫做构造函数的值。 这个函数会在我们使用new
创建类实例的时候被调用。 下面我们来看看,上面的代码被编译成JavaScript后是什么样子的:
let Greeter = (function () { function Greeter(message) { this.greeting = message;
}
Greeter.prototype.greet = function () { return "Hello, " + this.greeting;
}; return Greeter;
})(); let greeter;
greeter = new Greeter("world"); console.log(greeter.greet());
上面的代码里,let Greeter
将被赋值为构造函数。 当我们调用new
并执行了这个函数后,便会得到一个类的实例。 这个构造函数也包含了类的所有静态属性。 换个角度说,我们可以认为类具有实例部分与静态部分这两个部分。
让我们稍微改写一下这个例子,看看它们之前的区别:
class Greeter { static standardGreeting = "Hello, there";
greeting: string;
greet() { if (this.greeting) { return "Hello, " + this.greeting;
} else { return Greeter.standardGreeting;
}
}
} let greeter1: Greeter;
greeter1 = new Greeter(); console.log(greeter1.greet()); let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!"; let greeter2: Greeter = new greeterMaker(); console.log(greeter2.greet());
这个例子里,greeter1
与之前看到的一样。 我们实例化Greeter
类,并使用这个对象。 与我们之前看到的一样。
再之后,我们直接使用类。 我们创建了一个叫做greeterMaker
的变量。 这个变量保存了这个类或者说保存了类构造函数。 然后我们使用typeof Greeter
,意思是取Greeter类的类型,而不是实例的类型。 或者更确切的说,"告诉我Greeter
标识符的类型",也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数。 之后,就和前面一样,我们在greeterMaker
上使用new
,创建Greeter
的实例。
把类当做接口使用
如上一节里所讲的,类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
x: number;
y: number;
} interface Point3d extends Point {
z: number;
} let point3d: Point3d = {x: 1, y: 2, z: 3};
[转]TypeScript中文文档——接口类型 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
介绍
TypeScript的核心原则之一是对值所具有的shape进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
接口初探
下面通过一个简单示例来观察接口是如何工作的:
function printLabel(labelledObj: { label: string }) { console.log(labelledObj.label);
} let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
类型检查器会查看printLabel
的调用。 printLabel
有一个参数,并要求这个对象参数有一个名为label
类型为string
的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。 然而,有些时候TypeScript却并不会这么宽松,我们下面会稍做讲解。
下面我们重写上面的例子,这次使用接口来描述:必须包含一个label
属性且类型为string
:
interface LabelledValue {
label: string;
} function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label);
} let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
LabelledValue
接口就好比一个名字,用来描述上面例子里的要求。 它代表了有一个label
属性且类型为string
的对象。 需要注意的是,我们在这里并不能像在其它语言里一样,说传给printLabel
的对象实现了这个接口。我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件,那么它就是被允许的。
还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
下面是应用了“option bags”的例子:
interface SquareConfig {
color?: string;
width?: number;
} function createSquare(config: SquareConfig): {color: string; area: number} { let newSquare = {color: "white", area: 100}; if (config.color) {
newSquare.color = config.color;
} if (config.width) {
newSquare.area = config.width * config.width;
} return newSquare;
} let mySquare = createSquare({color: "black"});
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 比如,我们故意将createSquare
里的color
属性名拼错,就会得到一个错误提示:
interface SquareConfig {
color?: string;
width?: number;
} function createSquare(config: SquareConfig): { color: string; area: number } { let newSquare = {color: "white", area: 100}; if (config.color) { // Error: Property 'collor' does not exist on type 'SquareConfig' newSquare.color = config.collor; // Type-checker can catch the mistyped name here } if (config.width) {
newSquare.area = config.width * config.width;
} return newSquare;
} let mySquare = createSquare({color: "black"});
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用readonly
来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
你可以通过赋值一个对象字面量来构造一个Point
。 赋值后,x
和y
再也不能被改变了。
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScript具有ReadonlyArray<T>
类型,它与Array<T>
相似,只是把怕有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error! ro.push(5); // error! ro.length = 100; // error! a = ro; // error!
上面代码的最后一行,可以看到就算把整个ReadonlyArray
赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:
a = ro as number[];
readonly
vs const
最简单判断该用readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
。
额外的属性检查
我们在第一个例子里使用了接口,TypeScript让我们传入{ size: number; label: string; }
到仅期望得到{ label: string; }
的函数里。 我们已经学过了可选属性,并且知道他们在“option bags”模式里很有用。
然而,天真地将这两者结合的话就会像在JavaScript里那样搬起石头砸自己的脚。 比如,拿createSquare
例子来说:
interface SquareConfig {
color?: string;
width?: number;
} function createSquare(config: SquareConfig): { color: string; area: number } { // ... } let mySquare = createSquare({ colour: "red", width: 100 });
注意传入createSquare
的参数拼写为colour
而不是color
。 在JavaScript里,这会默默地失败。
你可能会争辩这个程序已经正确地类型化了,因为width
属性是兼容的,不存在color
属性,而且额外的colour
属性是无意义的。
然而,TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
// error: 'colour' not expected in type 'SquareConfig' let mySquare = createSquare({ colour: "red", width: 100 });
绕开这些检查非常简单。 最简便的方法是使用类型断言:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果SquareConfig
带有上面定义的类型的color
和width
属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
我们稍后会讲到索引签名,但在这我们要表示的是SquareConfig
可以有任意数量的属性,并且只要它们不是color
和width
,那么就无所谓它们的类型是什么。
还有最后一种跳过这些检查的方式,这可能会让你感到惊讶,它就是将这个对象赋值给一个另一个变量: 因为squareOptions
不会经过额外属性检查,所以编译器不会报错。
let squareOptions = { colour: "red", width: 100 }; let mySquare = createSquare(squareOptions);
要留意,在像上面一样的简单代码里,你可能不应该去绕开这些检查。 对于包含方法和内部状态的复杂对象字面量来讲,你可能需要使用这些技巧,但是大部额外属性检查错误是真正的bug。 就是说你遇到了额外类型检查出的错误,比如选择包,你应该去审查一下你的类型声明。 在这里,如果支持传入color
或colour
属性到createSquare
,你应该修改SquareConfig
定义来体现出这一点。
函数类型
接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) { let result = source.search(subString); if (result == -1) { return false;
} else { return true;
}
}
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 比如,我们使用下面的代码重写上面的例子:
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean { let result = src.search(sub); if (result == -1) { return false;
} else { return true;
}
}
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果你不想指定类型,Typescript的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc
类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是false
和true
)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc
接口中的定义不匹配。
let mySearch: SearchFunc;
mySearch = function(src, sub) { let result = src.search(sub); if (result == -1) { return false;
} else { return true;
}
}
可索引的类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]
或ageMap["daniel"]
。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:
interface StringArray {
[index: number]: string;
} let myArray: StringArray;
myArray = ["Bob", "Fred"]; let myStr: string = myArray[0];
上面例子里,我们定义了StringArray
接口,它具有索引签名。 这个索引签名表示了当用number
去索引StringArray
时会得到string
类型的返回值。
共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。
class Animal {
name: string;
} class Dog extends Animal {
breed: string;
} // Error: indexing with a 'string' will sometimes get you a Dog! interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
字符串索引签名能够很好的描述dictionary
模式,并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了obj.property
和obj["property"]
两种形式都可以。 下面的例子里,name
的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型 name: string // 错误,`name`的类型不是索引类型的子类型 }
最后,你可以将索引签名设置为只读,这样就防止了给索引赋值:
interface ReadonlyStringArray {
readonly [index: number]: string;
} let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
你不能设置myArray[2]
,因为索引签名是只读的。
类类型
实现接口
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date;
} class Clock implements ClockInterface {
currentTime: Date; constructor(h: number, m: number) { }
}
你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime
方法一样:
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
} class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) { this.currentTime = d;
} constructor(h: number, m: number) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
类静态部分与实例部分的区别
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:
interface ClockConstructor { new (hour: number, minute: number);
} class Clock implements ClockConstructor {
currentTime: Date; constructor(h: number, m: number) { }
}
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口,ClockConstructor
为构造函数所用和ClockInterface
为实例方法所用。 为了方便我们定义一个构造函数createClock
,它用传入的类型创建实例。
interface ClockConstructor { new (hour: number, minute: number): ClockInterface;
} interface ClockInterface {
tick();
} function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface { return new ctor(hour, minute);
} class DigitalClock implements ClockInterface { constructor(h: number, m: number) { }
tick() { console.log("beep beep");
}
} class AnalogClock implements ClockInterface { constructor(h: number, m: number) { }
tick() { console.log("tick tock");
}
} let digital = createClock(DigitalClock, 12, 17); let analog = createClock(AnalogClock, 7, 32);
因为createClock
的第一个参数是ClockConstructor
类型,在createClock(AnalogClock, 7, 32)
里,会检查AnalogClock
是否符合构造函数签名。
扩展接口
和类一样,接口也可以相互扩展。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface Shape {
color: string;
} interface Square extends Shape {
sideLength: number;
} let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string;
} interface PenStroke {
penWidth: number;
} interface Square extends Shape, PenStroke {
sideLength: number;
} let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
混合类型
先前我们提过,接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
} function getCounter(): Counter { let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { }; return counter;
} let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
在使用JavaScript第三方库的时候,你可能需要像上面那样去完整地定义类型。
接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
这是很有用的,当你有一个很深层次的继承,但是只想你的代码只是针对拥有特定属性的子类起作用的时候。子类除了继承自基类外与基类没有任何联系。 例:
class Control { private state: any;
} interface SelectableControl extends Control {
select(): void;
} class Button extends Control {
select() { }
} class TextBox extends Control {
select() { }
} class Image {
select() { }
} class Location {
select() { }
}
在上面的例子里,SelectableControl
包含了Control
的所有成员,包括私有成员state
。 因为state
是私有成员,所以只能够是Control
的子类们才能实现SelectableControl
接口。 因为只有Control
的子类才能够拥有一个声明于Control
的私有成员state
,这对私有成员的兼容性是必需的。
在Control
类内部,是允许通过SelectableControl
的实例来访问私有成员state
的。 实际上,SelectableControl
就像Control
一样,并拥有一个select
方法。 Button
和TextBox
类是SelectableControl
的子类(因为它们都继承自Control
并有select
方法),但Image
和Location
类并不是这样的。
[转]TypeScript中文文档——变量声明 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
因为TypeScript是JavaScript的超集,所以它本身就支持
如果你之前使用JavaScript时没有特别在意,那么这节内容会唤起你的回忆。 如果你已经对
一直以来我们都是通过
大家都能理解,这里定义了一个名为
我们也可以在函数内部定义变量:
并且我们也可以在其它函数内部访问相同的变量。
上面的例子里,
对于熟悉其它语言的人来说,
有些读者可能要多看几遍这个例子。 变量
这些作用域规则可能会引发一些错误。 其中之一就是,多次声明同一个变量并不会报错:
这里很容易看出一些问题,里层的
快速的猜一下下面的代码会返回什么:
介绍一下,
好吧,看一下结果:
很多JavaScript程序员对这种行为已经很熟悉了,但如果你很不解,你并不是一个人。 大多数人期望输出结果是这样:
还记得我们上面讲的变量获取吗?
每当
让我们花点时间考虑在这个上下文里的情况。
一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时
这种奇怪的形式我们已经司空见惯了。 参数
现在你已经知道了
主要的区别不在语法上,而是语义,我们接下来会深入研究。
当用
这里我们定义了2个变量
在
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里,但在直到声明它的代码之前的区域都属于时间死区。 它只是用来说明我们不能在
注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为ES2015,现代的运行时会抛出一个错误;然而,现今TypeScript是不会报错的。
关于时间死区的更多信息,查看这里Mozilla Developer Network.
我们提过使用
在上面的例子里,所有
并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告。
并不是说块级作用域变量不能在函数作用域内声明。 而是块级作用域变量需要在不用的块里声明。
在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。 例如,假设我们现在用
这个版本的循环能得到正确的结果,因为内层循环的
通常来讲应该避免使用屏蔽,因为我们需要写出清晰的代码。 同时也有些场景适合利用它,你需要好好打算一下。
在我们最初谈及获取用
因为我们已经在
回想一下前面
当
会输出与预料一致的结果:
它们与
这很好理解,它们引用的值是不可变的。
除非你使用特殊的方法去避免,实际上
现在我们有两种作用域相似的声明方式,我们自然会问到底应该使用哪个。 与大多数泛泛的问题一样,答案是:依情况而定。
使用最小特权原则,所有变量除了你计划去修改的都应该使用
另一方面,用户很喜欢
跟据你的自己判断,如果合适的话,与团队成员商议一下。 Fortunately, TypeScript allows you to specify that members of an object are
Another TypeScript已经可以解析其它 ECMAScript 2015 特性了。 完整列表请参见 the article on the Mozilla Developer Network。 本章,我们将给出一个简短的概述。
最简单的解构莫过于数组的解构赋值了:
这创建了2个命名变量
解构作用于已声明的变量会更好:
作用于函数参数:
你可以使用
当然,由于是JavaScript, 你可以忽略你不关心的尾随元素:
或其它元素:
你也可以解构对象:
这通过
就像数组解构,你可以用没有声明的赋值:
注意,我们需要用括号将它括起来,因为Javascript通常会将以
你也可以给属性以不同的名字:
这里的语法开始变得混乱。 你可以将
令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
默认值可以让你在属性为 undefined 时使用缺省值:
现在,即使
解构也能用于函数声明。 看以下简单的情况:
但是,通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要知道在设置默认值之前设置其类型。
其次,你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道
要小心使用解构。 从前面的例子可以看出,就算是最简单的解构也会有很多问题。 尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。 解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。
变量声明
let
和const
是JavaScript里相对较新的变量声明方式。 像我们之前提到过的,let
在很多方面与var
是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。 const
是对let
的一个增强,它能阻止对一个变量再次赋值。
let
和const
。 下面我们会详细说明这些新的声明方式以及为什么推荐使用它们来代替var
。
var
声明的怪异之处了如指掌,那么你可以轻松地略过这节。
var
声明
var
关键字定义JavaScript变量。
var a = 10;
a
值为10
的变量。
function f() { var message = "Hello, world!"; return message;
}
function f() { var a = 10; return function g() { var b = a + 1; return b;
}
} var g = f();
g(); // returns 11;
g
可以获取到f
函数里定义的a
变量。 每当g
被调用时,它都可以访问到f
里的a
变量。 即使当g
在f
已经执行完后才被调用,它仍然可以访问及修改a
。
function f() { var a = 1;
a = 2; var b = g();
a = 3; return b; function g() { return a;
}
}
f(); // returns 2
作用域规则
var
声明有些奇怪的作用域规则。 看下面的例子:
function f(shouldInitialize: boolean) { if (shouldInitialize) { var x = 10;
} return x;
}
f(true); // returns '10' f(false); // returns 'undefined'
x
是定义在if
语句里面,但是我们却可以在语句的外面访问它。 这是因为var
声明可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问(我们后面会详细介绍),包含它的代码块对此没有什么影响。 有些人称此为var
作用域或函数作用域。 函数参数也使用函数作用域。
function sumMatrix(matrix: number[][]) { var sum = 0; for (var i = 0; i < matrix.length; i++) { var currentRow = matrix[i]; for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
} return sum;
}
for
循环会覆盖变量i
,因为所有i
都引用相同的函数作用域内的变量。 有经验的开发者们很清楚,这些问题可能在代码审查时漏掉,引发无穷的麻烦。
变量获取怪异之处
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
setTimeout
会在若干毫秒的延时后执行一个函数(等待其它代码执行完毕)。
10
10
10
10
10
10
10
10
10
10
0
1
2
3
4
5
6
7
8
9
g
被调用时,它都可以访问到f
里的a
变量。
setTimeout
在若干毫秒后执行一个函数,并且是在for
循环结束后。 for
循环结束后,i
的值为10
。 所以当函数被调用的时候,它会打印出10
!
i
的值:
for (var i = 0; i < 10; i++) { // capture the current state of 'i' // by invoking a function with its current value (function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
i
会覆盖for
循环里的i
,但是因为我们起了同样的名字,所以我们不用怎么改for
循环体里的代码。
let
声明
var
存在一些问题,这恰好说明了为什么用let
语句来声明变量。 除了名字不同外,let
与var
的写法一致。
let hello = "Hello!";
块作用域
let
声明一个变量,它使用的是词法作用域或块作用域。 不同于使用var
声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块或for
循环之外是不能访问的。
function f(input: boolean) { let a = 100; if (input) { // Still okay to reference 'a' let b = a + 1; return b;
} // Error: 'b' doesn't exist here return b;
}
a
和b
。 a
的作用域是f
函数体内,而b
的作用域是if
语句块里。
catch
语句里声明的变量也具有同样的作用域规则。
try { throw "oh no!";
} catch (e) { console.log("Oh well.");
} // Error: 'e' doesn't exist here console.log(e);
let
语句之前访问它们,幸运的是TypeScript可以告诉我们这些信息。
a++; // illegal to use 'a' before it's declared; let a;
function foo() { // okay to capture 'a' return a;
} // 不能在'a'被声明前调用'foo' // 运行时应该抛出错误 foo(); let a;
重定义及屏蔽
var
声明时,它不在乎你声明多少次;你只会得到1个。
function f(x) { var x; var x; if (true) { var x;
}
}
x
的声明实际上都引用一个相同的x
,并且这是完全有效的代码。 这经常会成为bug的来源。 好的是,let
声明就不会这么宽松了。
let x = 10; let x = 20; // 错误,不能在1个作用域里多次声明`x`
function f(x) { let x = 100; // error: interferes with parameter declaration } function g() { let x = 100; var x = 100; // error: can't have both declarations of 'x' }
function f(condition, x) { if (condition) { let x = 100; return x;
} return x;
}
f(false, 0); // returns 0 f(true, 0); // returns 100
let
重写之前的sumMatrix
函数。
function sumMatrix(matrix: number[][]) { let sum = 0; for (let i = 0; i < matrix.length; i++) { var currentRow = matrix[i]; for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
} return sum;
}
i
可以屏蔽掉外层循环的i
。
块级作用域变量的获取
var
声明的变量时,我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲,每次进入一个作用域时,它创建了一个变量的环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。
function theCityThatAlwaysSleeps() { let getCity; if (true) { let city = "Seattle";
getCity = function() { return city;
}
} return getCity();
}
city
的环境里获取到了city
,所以就算if
语句执行结束后我们仍然可以访问它。
setTimeout
的例子,我们最后需要使用立即执行的函数表达式来获取每次for
循环迭代里的状态。 实际上,我们做的是为获取到的变量创建了一个新的变量环境。 这样做挺痛苦的,但是幸运的是,你不必在TypeScript里这样做了。
let
声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在setTimeout
例子里我们仅使用let
声明就可以了。
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
0
1
2
3
4
5
6
7
8
9
const
声明
const
声明是声明变量的另一种方式。
const numLivesForCat = 9;
let
声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与let
相同的作用域规则,但是不能对它们重新赋值。
const numLivesForCat = 9; const kitty = {
name: "Aurora",
numLives: numLivesForCat,
} // Error kitty = {
name: "Danielle",
numLives: numLivesForCat
}; // all "okay" kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
const
变量的内部状态是可修改的。 幸运的是,TypeScript允许你将对象的成员设置成只读的。 接口一章有详细说明。
let
vs. const
const
。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。 使用const
也可以让我们更容易的推测数据的流动。
let
的简洁性。 这个手册大部分地方都使用了let
。
readonly
. The chapter on Interfaces has the details.
解构
解构数组
let input = [1, 2]; let [first, second] = input; console.log(first); // outputs 1 console.log(second); // outputs 2
first
和 second
。 相当于使用了索引,但更为方便:
first = input[0];
second = input[1];
// swap variables [first, second] = [second, first];
function f([first, second]: [number, number]) { console.log(first); console.log(second);
}
f(input);
...name
语法创建一个剩余变量列表:
let [first, ...rest] = [1, 2, 3, 4]; console.log(first); // outputs 1 console.log(rest); // outputs [ 2, 3, 4 ]
let [first] = [1, 2, 3, 4]; console.log(first); // outputs 1
let [, second, , fourth] = [1, 2, 3, 4];
对象解构
let o = {
a: "foo",
b: 12,
c: "bar" } let {a, b} = o;
o.a
and o.b
创建了 a
和 b
。 注意,如果你不需要 c
你可以忽略它。
({a, b} = {a: "baz", b: 101});
{
起始的语句解析为一个块。
属性重命名
let {a: newName1, b: newName2} = o;
a: newName1
读做 "a
作为 newName1
"。 方向是从左到右,好像你写成了以下样子:
let newName1 = o.a; let newName2 = o.b;
let {a, b}: {a: string, b: number} = o;
默认值
function keepWholeObject(wholeObject: {a: string, b?: number}) { let {a, b = 1001} = wholeObject;
}
b
为 undefined , keepWholeObject
函数的变量 wholeObject
的属性 a
和 b
都会有值。
函数声明
type C = {a: string, b?: number} function f({a, b}: C): void { // ... }
function f({a, b} = {a: "", b: 0}): void {
// ...
}
f(); // ok, default to {a: "", b: 0}
C
的定义有一个 b
可选属性:
function f({a, b = 0} = {a: ""}): void {
// ...
}
f({a: "yes"}) // ok, default b = 0
f() // ok, default to {a: ""}, which then defaults b = 0
f({}) // error, 'a' is required if you supply an argument
[转]TypeScript中文文档——基础类型 TypeScripts
转自:https://zhongsp.gitbooks.io/typescript-handbook/content/
介绍
为了让程序有价值,我们需要能够处理最简单的数据单元:数字,字符串,结构体,布尔值等。 TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
布尔值
最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean
(其它语言中也一样)。
let isDone: boolean = false;
数字
和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是number
。 除了支持十进制和十六进制字面量,Typescript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6; let hexLiteral: number = 0xf00d; let binaryLiteral: number = 0b1010; let octalLiteral: number = 0o744;
字符串
JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用string
表示文本数据类型。 和JavaScript一样,可以使用双引号("
)或单引号('
)表示字符串。
let name: string = "bob";
name = "smith";
你还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围(`
),并且以${ expr }
这种形式嵌入表达式
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;
这与下面定义sentence
的方式效果相同:
let sentence: string = "Hello, my name is " + name + ".\n\n" + "I'll be " + (age + 1) + " years old next month.";
数组
TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[]
,表示由此类型元素组成的一个数组:
let list: number[] = [1, 2, 3];
第二种方式是使用数组泛型,Array<元素类型>
:
let list: Array<number> = [1, 2, 3];
元组 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为string
和number
类型的元组。
// Declare a tuple type let x: [string, number]; // Initialize it x = ['hello', 10]; // OK // Initialize it incorrectly x = [10, 'hello']; // Error
当访问一个已知索引的元素,会得到正确的类型:
console.log(x[0].substr(1)); // OK console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型 console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString x[6] = true; // Error, 布尔不是(string | number)类型
联合类型是高级主题,我们会在以后的章节里讨论它。
枚举
enum
类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。
enum Color {Red, Green, Blue}; let c: Color = Color.Green;
默认情况下,从0
开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从1
开始编号:
enum Color {Red = 1, Green, Blue}; let c: Color = Color.Green;
或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}; let c: Color = Color.Green;
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}; let colorName: string = Color[2];
alert(colorName);
任意值
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用any
类型来标记这些变量:
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
在对现有代码进行改写的时候,any
类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。 你可能认为Object
有相似的作用,就像它在其它语言中那样。 但是Object
类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法:
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check) let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
当你只知道一部分数据的类型时,any
类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
let list: any[] = [1, true, "free"];
list[1] = 100;
空值
某种程度上来说,void
类型像是与any
类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是void
:
function warnUser(): void {
alert("This is my warning message");
}
声明一个void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null
:
let unusable: void = undefined;
Null 和 Undefined
TypeScript里,undefined
和null
两者各自有自己的类型分别叫做undefined
和null
。 和void
相似,它们的本身的类型用处不是很大:
// Not much else we can assign to these variables! let u: undefined = undefined; let n: null = null;
默认情况下null
和undefined
是所有类型的子类型。 就是说你可以把null
和undefined
赋值给number
类型的变量。
然而,当你指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自。 这能避免很多常见的问题。 也许在某处你想传入一个string
或null
或undefined
,你可以使用联合类型string | null | undefined
。 再次说明,稍后我们会介绍联合类型。
注意:我们鼓励尽可能地使用
--strictNullChecks
,但在本手册里我们假设这个标记是关闭的。
Never
never
类型表示的是那些永不存在的值的类型。 例如,never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使any
也不可以赋值给never
。
下面是一些返回never
类型的函数:
// 返回never的函数必须存在无法达到的终点 function error(message: string): never { throw new Error(message);
} // 推断的返回值类型为never function fail() { return error("Something failed");
} // 返回never的函数必须存在无法达到的终点 function infiniteLoop(): never { while (true) {
}
}
类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
另一个为as
语法:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有as
语法断言是被允许的。
关于let
你可能已经注意到了,我们使用let
关键字来代替大家所熟悉的JavaScript关键字var
。 let
关键字是JavaScript的一个新概念,TypeScript实现了它。 我们会在以后详细介绍它,很多常见的问题都可以通过使用let
来解决,所以尽可能地使用let
来代替var
吧。
char a[] 和char *a的区别 C笔记
char a[] 和char *a的区别
char a[100] = "locck2";//OK char *b = "locck2" ;//OK
a和b都是指向字符串“locck2”的第一个字符的指针。
a数组是存放在栈中的,是可以通过指针修改数组内容的的;如:
a[1] = 'a';
而b指向的字符串是存放于常量区的,通过指针只能访问字符串常量,不能修改的。
char a[100] = "locck2";//在运行时才确认
而char *b="loccck2";在编译时就确定了,因为是常量。
内存分配方式
内存分配有三种:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。
- 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
- 栈区:在执行函数时,函数(包括main函数)内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(任何变量都处于站区,例如int a[] = {1, 2},变量a处于栈区。数组的内容也存在于栈区。)
- 堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,并立即将指针置位NULL,防止产生野指针。
KcrusKal、Prime最短路径算法 Unity游戏研究
克鲁斯卡尔算法,克鲁斯卡尔
给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 求最小生成树的算法
(1) 克鲁斯卡尔算法
图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.
(2) 普里姆算法
图的存贮结构采用邻接矩阵.此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树 .
下面来具体讲下:
克鲁斯卡尔算法
方法:将图中边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去.依次选够(n-1)条边,即得最小生成树.(n为顶点数)
第一步:由边集数组选第一条边
第二步:选第二条边,即权值为2的边
第三步:选第三条边,即权值为3的边
第四步:选第四条边,即权值为4的边
第五步:选第五条边
普里姆算法
方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中,表示该顶点已连通.再用集合内的顶点与集合外的顶点构成的边中找最小的边,并将相应的顶点加入集合中,如此下去直到全部顶点都加入到集合中,即得最小生成树.
例在下图中从1点出发求出此图的最小生成树,并按生成树的边的顺序将顶点与权值填入表中.
———————>先写出其邻接矩阵
第一步:从①开始,①进集合,用与集合外所有顶点能构成的边中找最小权值的一条边
①——②权6
①——③权1 -> 取①——③边
①——④权5 第二步:③进集合,①,③与②,④,⑤,⑥构成的最小边为
①——④权5
③——⑥权4 -> 取③——⑥边 第三步:⑥进集合,①,③,⑥与②,④,⑤构成的各最小边
①——②权6
③——②权5
⑥——④权2 -> 取⑥——④边 第四步:④进集合,①,③,⑥,④与②,⑤构成的各最小边
①——②权6
③——②权5 -> 取③——②边
⑥——⑤权6 第四步:②进集合,①,③,⑥,②,④与⑤构成的各最小边
②——⑤权3 -> 取②——⑤边
Lua中的table函数库 Lua笔记
一部分的table函数只对其数组部分产生影响, 而另一部分则对整个table均产生影响. 下面会分开说明.
table.concat(table, sep, start, end)
concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。除了table外, 其他的参数都不是必须的, 分隔符的默认值是空字符, start的默认值是1, end的默认值是数组部分的总长.
sep, start, end这三个参数是顺序读入的, 所以虽然它们都不是必须参数, 但如果要指定靠后的参数, 必须同时指定前面的参数.
> tbl = {"alpha", "beta", "gamma"}
> print(table.concat(tbl, ":"))
alpha:beta:gamma
> print(table.concat(tbl, nil, 1, 2))
alphabeta
> print(table.concat(tbl, "\n", 2, 3))
beta
gamma
table.insert(table, pos, value)
table.insert()函数在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
> tbl = {"alpha", "beta", "gamma"}
> table.insert(tbl, "delta")
> table.insert(tbl, "epsilon")
> print(table.concat(tbl, ", ")
alpha, beta, gamma, delta, epsilon
> table.insert(tbl, 3, "zeta")
> print(table.concat(tbl, ", ")
alpha, beta, zeta, gamma, delta, epsilon
table.maxn(table)
table.maxn()函数返回指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0. 此函数不限于table的数组部分.
> tbl = {[1] = "a", [2] = "b", [3] = "c", [26] = "z"}
> print(#tbl)
3 -- 因为26和之前的数字不连续, 所以不算在数组部分内
> print(table.maxn(tbl))
26
> tbl[91.32] = true
> print(table.maxn(tbl))
91.32
table.remove(table, pos)
table.remove()函数删除并返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起.
table.sort(table, comp)
table.sort()函数对给定的table进行升序排序.
> tbl = {"alpha", "beta", "gamma", "delta"}
> table.sort(tbl)
> print(table.concat(tbl, ", "))
alpha, beta, delta, gamma
comp是一个可选的参数, 此参数是一个外部函数, 可以用来自定义sort函数的排序标准.
此函数应满足以下条件: 接受两个参数(依次为a, b), 并返回一个布尔型的值, 当a应该排在b前面时, 返回true, 反之返回false.
例如, 当我们需要降序排序时, 可以这样写:
> sortFunc = function(a, b) return b < a end
> table.sort(tbl, sortFunc)
> print(table.concat(tbl, ", "))
gamma, delta, beta, alpha
用类似的原理还可以写出更加复杂的排序函数. 例如, 有一个table存有工会三名成员的姓名及等级信息:
guild = {}
table.insert(guild, {
name = "Cladhaire",
class = "Rogue",
level = 70,
})
table.insert(guild, {
name = "Sagart",
class = "Priest",
level = 70,
})
table.insert(guild, {
name = "Mallaithe",
class = "Warlock",
level = 40,
})
对这个table进行排序时, 应用以下的规则: 按等级升序排序, 在等级相同时, 按姓名升序排序.
可以写出这样的排序函数:
function sortLevelNameAsc(a, b)
if a.level == b.level then
return a.name < b.name
else
return a.level < b.level
end
end
测试功能如下:
> table.sort(guild, sortLevelNameAsc)
> for idx, value in ipairs(guild) do print(idx, value.name) end
1, Mallaithe
2, Cladhaire
3, Sagart
table.foreachi(table, function(i, v))
会期望一个从 1(数字 1)开始的连续整数范围,遍历table中的key和value逐对进行function(i, v)操作
t1 = {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="hello lua"};
table.foreachi(t1, function(i, v) print (i, v) end) ; --等价于foreachi(t1, print)
输出结果:
1 2
2 4
3 6
4 8
5 10
6 12
table.foreach(table, function(i, v))
与foreachi不同的是,foreach会对整个表进行迭代
t1 = {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="hello lua"};
table.foreach(t1, function(i, v) print (i, v) end) ;
输出结果:
1 2
2 4
3 6
4 8
5 10
6 12
web hello lua
language Lua
version 5
table.getn(table)
返回table中元素的个数
t1 = {1, 2, 3, 5};
print(getn(t1))
->4
table.setn(table, nSize)
设置table中的元素个数