TypeScript语法重点
撰写于 2019-02-03 修改于 2019-07-03 分类 TypeScript
ts语法
基本类型
布尔值
let isDone: boolean = false;
数字
let decLiteral: number = 5; let hexLiteral: number = oxf00d; let binaryLiteral: number = 0b1010; let octLiberal: number = 0o756;
字符串
let name: string = "jack"; //字符串拼接 let sentences: string = "Hello, my name is" + name + ".\n" + "My age is" + (age + 1) + ".";
数组
//普通定义 let list: number[] = [1, 2, 3]; //泛型定义 let list: Array<number> = [1, 2, 3];
元组
表示一个已知类型的数组,各元素的类型不必相同
let x:[string, number]; //正确初始化 x = ["hello", 10]; //错误初始化 x = [10, "hello"]; //访问已知索引的元素 console.log(x[0].substr(1)) //访问越界元素,会使用联合类型替代(string|number) x[3] = "world"; //"string"和"number"都有toString()函数 console.log(x[5].toString()); //不能把该元组的元素赋值为,除string|number之外的类型 x[6] = true; //error
枚举
//定义枚举类型,枚举值默认是从0开始的 enum Color {Red, Green, Blue}; //定义枚举变量 let c:Color = Color.Red; //自定义枚举值 enum Color {Red = 1,Green, Blue}; enum Color {Red = 1, Green = 5, Blue = 8}; //使用枚举名 let colorName: string = Color[5]; //"Green"
任意值
任意值:any,可以赋给该类型变量任意值,编译阶段不会去检查该类型的值。
let noSure: any = 4; noSure = "maybe a string instead"; noSure = true;
注意:any的作用同Object类型有相似之处,但是本质不同,any类型的变量,在编译阶段不会去检查类型,也不会检查方法是否存在,例如:
let notSure: any = 4; //以下方法,编译可以通过,编译器不会检查该方法是否存在,isItExists()可能在运行时存在 notSure.isItExists();
但是,Object是不行的,Object类似于所有类型的父类,所有类型的值都可以赋值给Object类型,但是在编译阶段会检查该值类型和方法调用是否正确,例如:
let prettySure: Object = 4; //错误,Object中没有toFiexd prettySure.toFiexd();
any的其他用法:
let list: any[] = [1, true, "free"]; list[1] = 100;
void、undefined、null、never
object
object
表示非原始类型,也就是除number
,string
,boolean
,sysmbol
,null
,undefined
之外的类型。类型断言
类型断言跟其他语言里面的类型转换很相似,都是进行类型转换,但是ts里面不会进行类型检查,类型检查交给了程序员来处理,明确告诉编译器:相信我,我知道自己在干嘛!,有两种形式的类型断言:
//1. 尖括号 let someVaue: any = "This is a string"; let strLength: number = (<string>someVlue).length; //2. as(推荐) let strLength: number = (someValue as string).length;
变量声明
- var声明
//函数外声明
var a = 10;
//函数内声明
function f() {
var message = "Hello world";
return message;
}
//闭包声明
function f() {
var a = 10;
return function() {
var b = a + 1;
return b;
}
}
var g = f();
g(); //return 11
var变量作用域
如果你用过c/c++,java等其他高级语言,你会发现ts/js中的变量的作用域很奇怪,这是应为ts/js中以前是没有块级作用域的概念,例如:
function f(isInit: boolean) { if (isInit) { var x = 10; } return x; } f(true); //return 10 f(false); //undefined
你会发现,虽然
var x = 10
定义在if
语句块中,但是块外依旧可以访问,这是因为var
声明的变量只有两种作用域:1.全局作用域 2.函数作用域
。这种方式虽然有方便的地方,但是也会引发很多问题:function sumMatrix(matrix: num[][]) { 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; }
如果以上逻辑运行在
C/C++
平台上,没有一点问题,因为内层for
循环的i
会屏蔽掉外层for
的i
,但是在ts/js
中,这就会引发重大问题,在函数内无论声明多少次i
,都是引用的同一个变量。还有一个诡异的问题:
for (var i = 0; i < 10; i++) { //setTimeout延迟若干秒后,执行函数 setTimeout(function() {console.log(i);}, 100 * i}; }
运行以上函数会输出:
10 10 10 10 10 10 10 10 10 10
而不是预想中的:0 1 2 3 4 5 6 7 8 9
这是因为:循环10次,引用的都是同一个i,循环10次后,i 为10,所有的函数使用的都是同一个i,所以输出的都是10!
如何处理呢?只需每次循环的时候,为每次i创建一个备份,单独创建一个函数环境:for (var i = 0; i < 10; i++) { function(i) { setTimeout(function() {console.log(i);}, 100 * i}; }(i); }
let 声明
正是因为var
带来了很多不方便的地方,所以let
就出现了,它弥补了var
块级作用域的缺失。使用方法,同var
相同function f(input: boolean) { let a = 100; if (input) { let b = a + 1; return b; } //错误,b没有定义,因为b是let声明的,只在 if 语句中可见 return b; }
用
var
声明的变量,无论在何处声明,都可以,但是let
不行,let声明的变量必须要在使用前声明//var声明的变量,这样写没有问题 a = a + 1; var a = 10; //但是let声明的变量就不行, 会报错: b is not defined b = b + 1; let b = 10;
重定义
var 声明的变量,无论声明多少次,都不会报错,都是引用的同一个变量://以下正常运行 function f(x) { var x; var x = 10; if (true) { var x; } }
let声明的变量不允许出现这种情况,相同的作用域中,不能出现重定义。
//错误! function f(x) { let x = 10; } //错误! function f() { let x = 10; var x = 10; }
但是在不同的作用域中,let声明的变量,允许出现相同定义,内层定义会屏蔽外层定义。
function g(isInit: boolean) { var x = 10; if (isInit) { let x = 20; return x; } return x; } g(true); //return 20; g(false); //return 10;
总之,let的使用方式,跟C/C++,Java等其他高级语言默认变量声明,是相似的。
const
就是常量,被第一次赋值以后不允许再赋值。跟let有相同的作用域规则。
注意:只是变量不允许第二次赋值,但是里面包含的成员变量,是不受限制的。当然也可以将成员变量设置为只读的。const kitty = { name: "Aurora", numLives: 9, }; //kitty不允许再进行赋值,再次进行赋值操作会报错。 kitty = { name = "Danielle", numLives: 12, }; //但是可以修改kitty.name 和 kitty.numLives的值。 kitty.name = "Danielle"; kitty.numLives = 12;
解构
解构赋值是
ts/js
的一种表达式,可以将数组中的值或者对象的属性解包为不同的变量数组解构
//基本的数组解构 var foo = ["one", "two", "three"]; var [one, two, tree] = foo; console.log(one); //"one" console.log(two); //"two" console.log(three); //"three" //声明与赋值分开 var a, b; [a, b] = [1, 2]; console.log(a);//1 console.log(b);//2 //默认值 var a, b; [a=5, b=7] = [1]; console.log(a);//1 console.log(b);//7 //交换值 var a = 1; var b = 3; [a, b] = [b, a]; console.log(a); //3 console.log(b); //1 //函数返回值解构 function f() { return [1, 2]; } var a,b; [a, b] = f(); console.log(a); //1 console.log(b); //2 //忽略一些值 function f() { return [1, 2, 3]; } var [a, , b] = f(); console.log(a); //1 console.log(b); //3 //把一个数组的剩余部分赋值给一个变量 var [a, ...b] = [1, 2, 3]; console.log(a); //1 console.log(b); //[2, 3] //剩余部分的变量赋值(即...b),不允许后面跟一个空逗号,所以下面的使用是错误的 var [a, ...b,] = [1, 2, 3]; //从一个正则表达式匹配项里面解包(操作都一样) function parseProtocol(url) { var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url); if (!parsedURL) { return false; } console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"] var [, protocol, fullhost, fullpath] = parsedURL; return protocol; } console.log(parseProtocol('https://developer.mozilla.org/en-US/Web/JavaScript')); // "https"
对象解构
//基础赋值 var o = {p:42, q:true}; var {p, q} = o; console.log(p); //42 console.log(q); //true //不声明,直接赋值 var a, b; //注:当在没有声明的情况下使用对象字面量解构,则必选要加上()。因为如果不加(),左边会被认为是一个语句块,并不会被认定为一个对象字面量。 //相当于:var {a, b} = {a:1, b:2} ({a, b} = {a:1, b:2}); //赋值给一个新的变量名 var o = {p:42, q:true}; var {p: foo, q: bar} = 0; console.log(foo); //42 console.log(bar); //true //默认值 var {a = 10, b = 5} = {a:3}; console.log(a); // 3 console.log(b); // 5 //新的变量名,给出默认值 var {a: aa = 10, b: bb = 5} = {a: 3}; console.log(aa); //3 console.log(bb); //5 //设置函数参数的默认值 function drawES2015Chart({size = 'big', cords = {x: 0, y: 0}, radius = 25} = {}) { console.log(size, cords, radius); // do some chart drawing } drawES2015Chart({ cords: {x: 18, y: 30}, radius: 30 }); //嵌套形式对象的解构 var metadata = { title: 'Scratchpad', translations: [ { locale: 'de', localization_tags: [], last_edit: '2014-04-14T08:43:37', url: '/de/docs/Tools/Scratchpad', title: 'JavaScript-Umgebung' } ], url: '/en-US/docs/Tools/Scratchpad' }; var {title: englishTitle, translations: [{title: localeTitle}]} = metadata; console.log(englishTitle); // "Scratchpad" console.log(localeTitle); // "JavaScript-Umgebung" //for解构 var people = [ { name: 'Mike Smith', family: { mother: 'Jane Smith', father: 'Harry Smith', sister: 'Samantha Smith' }, age: 35 }, { name: 'Tom Jones', family: { mother: 'Norah Jones', father: 'Richard Jones', brother: 'Howard Jones' }, age: 25 } ]; for (var {name: n, family: {father: f}} of people) { console.log('Name: ' + n + ', Father: ' + f); } // "Name: Mike Smith, Father: Harry Smith" // "Name: Tom Jones, Father: Richard Jones" //解包对象字段,传递给函数参数 function userId({id}) { return id; } function whois({displayName, fullName: {firstName: name}}) { console.log(displayName + ' is ' + name); } var user = { id: 42, displayName: 'jdoe', fullName: { firstName: 'John', lastName: 'Doe' } }; console.log('userId: ' + userId(user)); // "userId: 42" whois(user); // "jdoe is John" //计算出对象属性名字,然后解构 let key = 'z'; let {[key]: foo} = {z: 'bar'}; console.log(foo); // "bar" //对象解构的剩余部分(剩余部分,必须是可枚举的) let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40} a; // 10 b; // 20 rest; // { c: 30, d: 40 } //非法js标识符作为属性名 //通过提供有效的替代标识符,可以将解构与非法js标识符的属性名一起使用。 const foo = { 'fizz-buzz': true }; const { 'fizz-buzz': fizzBuzz } = foo; console.log(fizzBuzz); // "true"
展开
展开操作符与解构相反,讲一个数组展开为另一个数组,或者将一个对象展开为另外一个对象:
//数组 let first = [1, 2]; let second = [3, 4]; let bothPlus = [0, ...first, ..second, 5]; //[0, 1, 2, 3, 4, 5],对first,second进行浅拷贝 //对象: //对象的展开比数组的展开要复杂的多。 像数组展开一样,它是从左至右进行处理,但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。 let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { ...defaults, food: "rich" }; //{ food: "rich", price: "$$", ambiance: "noisy" } //展开的对象,里面的属性必须是可枚举属性,才可以被展开,方法会被丢失掉 class C { p = 12; m() { } } let c = new C(); let clone = { ...c }; clone.p; // ok clone.m(); // error!
接口
介绍
ts的接口定义,还是跟其他语言的接口,有点区别的。ts里,接口的就是定义契约(约束)。
//基本用法 interface LabelValue { label: string; } function printLabel(labelObj: LabelValue) { console.log(labelObj.lable); } let myObj = {size: 10, label: "Size 10 Object"}; printLable(myObj);
注:上面的代码运行起来没有问题,但是会有一个奇怪的问题就是:如果不定义
let myObj = {size: 10, label: "Size 10 Object"}
,直接使用printLable({size: 10, label: "Size 10 Object"})
,会报错:Argument of type '{ size: number; lable: string; }' is not assignable to parameter of type 'LabelValue'.Object literal may only specify known properties, and 'size' does not exist in type 'LabelValue'
。大致的意思是:对象字面量只能指定已知属性。(暂时不知道什么意思)可选属性
有些情况下接口里面的属性,只是在某些条件下存在,有的时候不存在,可以使用可选属性。
//可选属性 interface SquareCongfig { color?: string; width?: number; } function createSquare(config: SquareCongfig): { 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; } //也可用展开 //let newSquare = { color: "white", area: 100, ...config }; return newSquare; } let mySquare = createSquare({ color: "black" }) console.log(mySquare.color); console.log(mySquare.area);
只读属性
顾名思义,对象中属性的值不能被修改,用
readonly
修饰属性即可interface Point { readonly x: number; readonly y: number; } let point: Point = {x: 10, y: 20}; //error: Cannot assign to 'x' because it is a constant or a read-only property. point.x = 5
数组也可以只读,只需使用
ReadonlyArray<T>
定义数组即可,使用方法跟Array<T>
一样,但是去掉了所有可修改值得方法。let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; //error ro.push(5); //error ro[0] = 12; //error ro.length = 10; //error a = ro; //可以强转 a = ro as number[];
注:变量用
const
, 属性用readonly
函数类型
//函数类型 interface SearchFunc { (source: string, subString: string): number; } //使用 let mySearch: SearchFunc = function (source: string, subString: string) { let result = source.search(subString); return result; } //不加参数类型,依旧可以根据接口判断出来 let otherSearch: SearchFunc = function (src, sub) { return src.search(sub); }
可索引类型
索引签名表示:当时用
number
去索引 对象或者数组时,会返回string
类型的返回值。//可索引类型 interface StringArray { [index: number]: string; } let myArray: StringArray = ["Bob", "Fred"]
支持两种类型的索引:
string
和number
。可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。这是因为:当时用number来索引时,js会将它转换为string然后再去索引对象,也就是用100去索引和用“100”去索引,两者是等效的,因此,两者要保持一致。类类型
//接口描述了公共部分,而不是公有和私有部分。 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 Shape { color: string; (): number; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; } let square = <Square> {}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0;
混合接口类型
//一个对象可以同时作为函数和对象使用,并带有额外的属性 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;
类
ts的类跟 java
的类很相似。
继承
//继承 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("Golloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
权限:public, private, protected
readonly: 修饰的属性为只读。
参数属性
参数属性算是一个语法糖,在构造函数的参数部分,定义并初始化一个成员。
class Animal { //相当于定义了: priavte name: string constructor(private name: string) {} move(distanceInMeters: number) { console.log(this.name + " moved " + distanceInMeters + "m."); } }
存取器:getters/setters
let password = "secret passcode"; class Employee { private _fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (password && password == "secret passcode") { this._fullName = newName; } else { console.log("Error: Can't update name!"); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
静态属性: static
抽象类: abstract
构造函数:
上面例子中,我们使用 let employee = new Employee(),创建一个Employee类型的实例,这个Employee其实是一个构造函数,这个构造函数包含了类所有的静态属性。
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());
类当接口使用
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
函数
- 定义
//定义
function add(x: number, y: number) {
return x + y;
}
//或者
let add = function(x: number, y: number): number {return x + y;}
//完整定义(参数名字无所谓,重在函数的签名;没有返回值,也要写void)
let add: (x: number, y: number) => number = function(x: number, y: number): number {return x + y;};
可选参数
在js中,可以传递任意数量的参数给函数,但是ts不行,传递的函数参数数量,必须要符合函数的定义。
//函数类型为:(firstName: string, lastName?: string) => string 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
参数默认值:同上
剩余参数
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); //函数类型 let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
高级类型
交叉类型: typeA & typeB
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 PersonOne { constructor(public name: string) {} } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() {} } let jim = extend(new PersonOne("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
联合类型: typeA | typeB
限制数据类型只能是 typeA 或者 typeB
function padLeft(value: string, padding: string | number) { // ... } //Error!只能传入string或者number let indentedString = padLeft("Hello world", true);
还有一种情况,当一个变量的类型是 typeA | typeB,那么该变量包含了A和B共有的成员。
interface Bird { fly(): layEggs(); } interface Fish { swim(): layEggs(); } function getSmallPet(): Fish | Bird { //... } let pet = getSmallPet(); pet.layEggs();//Okay! pet.swim(); //Error!
怎么让以上代码没有错误呢,使用类型断言:
if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); }
可迭代性
当一个对象实现了Symbol.iterator属性时,我们认为他是可迭代性的。Array,Map,Set,String,Int32Array,Uint32Array都是实现了各自的Symbol.iterator。
for…of 值的遍历 (注意对象不能使用!!)
let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); }
for…in 键的遍历
for (let index in someArray) { console.log(index); // }