TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript 扩展了 JavaScript 的句法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。TypeScript 是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
$ npm install -g typescript
$ tsc app.ts # app.ts => app.js
let isDone: boolean = false; // tsc => var isDone = false;
let count: number = 10; // tsc => var count = 10
let name: string = 'Semliker'; // tsc => var name = 'Semlinker'
let list: number[] = [1,2,3]; // tsc => var list = [1,2,3];
let list: Array<number> = [1,2,3];
// tsc => var list = [1,2,3];
enum Direction {
NORTH,
SOUTH,
EAST,
WEST
};
let dir: Direction = Direction.NORTH;
默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。
当然我们也可以设置 NORTH 的初始值,比如:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST
};
在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
NORTH = 'NORTH',
SOUTH = 'SOUTH',
EAST = 'EAST',
WEST = 'WEST',
}
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
=> tsc =>
var notSure = 4;
notSure = "maybe a string instead";
notSure = false;
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为 string
和 number
类型的元组。
let x: [string, number];
x = ['semlinker', 10]; // 正常赋值
x = [10, 'semlinker']; // 类型不匹配
当访问一个已知索引的元素,会得到正确的类型:
console.log(x[0].substr(1)); // OK
// Error, 'number' does not have 'substr' method
console.log(x[1].substr(1));
当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world'; // OK, 字符串可以赋值给(string | number) 类型
console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString 方法
x[6] = true; // Error, 布尔不是(string | number) 类型
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}
=> tsc =>
function warnUser() {
console.log("This is my warning message");
}
需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined
或 null
:
let unusable: void = undefined;
TypeScript 里,undefined
和null
两者各自有自己的类型分别叫做 undefined
和null
。
let u: undefined = undefined;
let n: null = null;
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null
和 undefined
赋值给 number
类型的变量。然而,当你指定了--strictNullChecks
标记,null
和 undefined
只能赋值给 void
和它们各自。
never
类型表示的是那些永不存在的值的类型。 例如,never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {}
}
有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,”相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
类型断言有两种形式:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
let greet = (message: string | string[]) => {
if(message instanceof Array) {
let messages = "";
message.forEach((msg) => {
messages += ` ${msg}`;
});
console.log("Received messages ", messages);
} else {
console.log("Received message = ", message);
}
};
greet('semlinker');
greet(['Hello', 'Angular']);
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
TypeScript | JavaScript |
---|---|
Types | No types |
Arrow function | Arrow function (ES2015) |
Function types | No Function types |
Required and Optional parameters | All parameters are optional |
Default parameters | Default parameters |
Rest parameters | Rest parameters |
Overloaded function | No overloaded functions |
myBooks.forEach(() => console.log('Done reading'));
myBooks.forEach(title => console.log(title));
myBooks.forEach((title, idx, arr) =>
console.log(idx + '-' + title);
);
myBooks.forEach((title, idx, arr) => {
console.log(idx + '-' + title);
});
// 未使用箭头函数
function Book() {
let self = this;
self.publishDate = 2016;
setInterval(function() {
console.log(self.publishDate);
}, 1000);
}
// 使用箭头函数
function Book() {
this.publishDate = 2016;
setInterval(() => {
console.log(this.publishDate);
}, 1000);
}
function createUserId(name: string, id: number): string {
return name + id;
}
let IdGenerator: (chars: string, nums: number) => string;
function createUserId(name: string, id: number): string {
return name + id;
}
IdGenerator = createUserId;
// 可选参数
function createUserId(name: string, age?: number,
id: number): string {
return name + id;
}
// 默认参数
function createUserId(name: string = 'Semlinker', age?: number,
id: number): string {
return name + id;
}
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
let x: number, let y: number ,let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
let two_array = [0,1];
let five_array = [...two_array,2,3,4];
let colors: string[] = ["red", "green", "blue"];
for(let i in colors) {
console.log(i);
}
let person = {
name: 'Semlinker',
gender: 'male'
};
let {name, gender} = person;
let person = {
name: 'Semlinker',
gender: 'male',
address: 'Xiamen'
};
// 组装对象
let personWithAge = {...person, age: 31};
// 获取除了某些项外的其它项
let {name, ...rest} = person;
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {
name: string;
age: number;
}
let semlinker: Person = {
name: 'Semlinker',
age: 31
};
interface Person {
readonly name: string;
age?: number;
}
只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 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!
在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。
在 TypeScript 中,我们可以通过 Class
关键字来定义一个类:
class Greeter {
static cname: string = 'Greeter'; // 静态属性
greeting: string; // 成员属行
constructor(message: string) { // 构造函数 - 执行初始化操作
this.greeting = message;
}
static getClassName() { // 静态方法
return 'Class name is Greeter';
}
greet() { // 成员方法
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
在 TypeScript 中,我们可以通过 getter
和 setter
方法来实现数据的封装和有效性校验,防止出现异常数据。
let passcode = "hello angular 5";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "hello angular 5") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
继承 (Inheritance) 是一种联结类与类的层次模型。指的是一个类 (称为子类、子接口) 继承另外的一个类 (称为父类、父接口) 的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种 is-a 关系。
在 TypeScripe 中,我们可以通过 extends
关键字来实现继承:
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);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。
interface GenericIdentityFn<T> {
(arg: T): T;
}
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; };
interface Hero { // Hero 接口
id: number;
name: string;
}
getHeroes(): Observable<Hero[]> {
return Observable.of([
{ id: 1, name: 'Windstorm' },
{ id: 13, name: 'Bombasto' },
{ id: 15, name: 'Magneta' },
{ id: 20, name: 'Tornado' }
]);
}
上面 getHeroes(): Observable<Hero[]>
表示调用 getHeroes()
方法后返回的是一个 Observable 对象,<Hero[]>
用于表示该 Observable 对象的观察者,将会收到的数据类型。示例中表示将会返回 <Hero[]>
英雄列表。
compilerOptions 支持很多选项,常见的有 baseUrl
、 target
、baseUrl
、 moduleResolution
和 lib
等。
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
],
"paths": {
"ngx-example-library": [
"dist/ngx-example-library"
]
}
},
"angularCompilerOptions": {
"preserveWhitespaces": false
}
}
(示例来源 - angular6-example-app)
使用驼峰(camelCase)命名变量和函数名
Bad
var FooVar;
function BarFunc() { }
Good
var fooVar;
function barFunc() { }
使用帕斯卡(PascalCase)命名类名
Bad
class foo { }
Good
class Foo { }
使用帕斯卡(PascalCase)命名类成员与方法
Bad
class Foo {
Bar: number;
Baz() { }
}
Good
class Foo {
bar: number;
baz() { }
}
I
前缀Bad
interface IFoo { }
Good
interface Foo { }
使用帕斯卡(PascalCase)命名
Bad
namespace foo { }
Good
namespace Foo { }
使用帕斯卡(PascalCase)命名枚举
Bad
enum color { }
Good
enum Color { }
使用帕斯卡(PascalCase)命名枚举成员
Bad
enum Color {
red
}
Good
enum Color {
Red
}
最好不好显式使用不可用的值
Bad
let foo = { x:123, y:undefined };
Good
let foo: { x:number, y?:number } = { x:123 };
通常使用 undefined( 而不是返回一个类似于 { valid:boolean, value?:Foo } 的对象 )
Bad
return null;
Good
return undefined;
参考 Node.js 回调函数 Error First 风格(若未发生异常,error 参数值设置为 null)
Bad
cb(undefined)
Good
cb(null)
避免使用值比较判断对象是否为 null 或 undefined
Bad
if (error === null)
Good
if (error)
声明数组时使用 foos:Foo[]
而不是 foos:Array<Foo>
,便于阅读
当你需要复合类型时,使用 type
type Foo = number | { someProperty: number }
当你需要继承或实现时,使用 interface
interface Foo {
foo: string;
}
interface FooBar extends Foo {
bar: string;
}
class X implements FooBar {
foo: string;
bar: string;
}
(x) => x + x
是错误的,下面是正确的做法:x => x + x
(x,y) => x + y
<T>(x: T, y: T) => x === y
{}
把循环体和条件语句括起来。for (let i = 0, n = str.length; i < 10; i++) { }
if (x < 10) { }
function f(x: number, y: string): void { }
let x = 1; var y = 2;
而不是 let x = 1, y = 2;
)。void