温故而知新,回顾前文学到的数据类型:boolean、number、string、void、null、undefined、any,然后是类型:联合类型、接口类型,本文继续来学习TypeScript,相信我会越学越上头。
基础
数组
数组的类型表示比较灵活
类型声明
直接使用类型[]
的方式来约束数组的类型,数组中的数据必须满足类型约束。类型可以是单个类型,也可是联合类型
let isNumberArr: number[] = [1,2,3];
// let isNumberArr: number[] = [1,2,'3']; // 数组中必须满足类型约束,不允许出现其他类型的数据
// let isNumberArr: (number | string)[] = [1,2,'3']; // 联合类型
使用泛型
我们也可以使用数组泛型来约束类型
let isArr: Array<number> = [1,2,3];
// let isArr: Array<number | string> = [1,2,'3'];
使用接口
接口可用来表示一种类型,数组也可以使用。采用接口的方式可以其他地方使用
interface NumberArray {
[index: number]: number
}
let isNumArr:NumberArray = [1,2,3];
类数组
类数组,不是普通的数组,只是类似数组。一般来说,类数组会采用接口的方式定义。
interface IArgument {
[index: number]: string,
length: number,
callee: Function,
}
function sum(x:number, y:number) {
let args:IArgument = arguments
console.log(arguments, args[0]);
}
sum(1, 2)
细心的朋友可能会发现,接口中好像有一个任意属性,其实不是的。任意属性的key只能是字符串类型,本处是number类型,更像是数组的索引。这就是类数组,看起来像,但又不完全是。
函数
JavaScript中,函数可算得上是“亲儿子”,重要地位非同一般。定义函数一般有两种方式:function声明、表达式定义。如果需要使用ts对函数进行约束,那么其输入和输出都应该考虑到。
function sum1(x: number, y: number): number {
return x + y;
} // function
let sum2 = (x: number, y: number): number => {
return x + y;
} // 表达式
也可以使用接口来定义函数的形式。
interface SumFuc {
(x: number, y: number): number;
}
let sum3: SumFuc = function(x: number, y: number) {
return x + y;
}
可选参数
类似于对象的可选属性,使用?来声明,值得注意的是可选参数后面不能再出现必需参数,并且可选参数在必需参数后面。
function sum4(x: number, y: number, z?: number): number {
if (z) {
return x + y + z;
}
return x + y;
}
默认参数
可以给函数的参数添加默认值,ts会将添加了默认值的参数识别为可选参数,此时默认参数并没有位置要求,其后也可以跟必需参数。
function sum5(x: number = 10, y: number, z?: number): number {
if (z) {
return x + y + z;
}
return x + y;
}
剩余参数
有时候参数很多,或者不确定有哪些参数,可以采用剩余参数的方式将这些参数都放在一个参数列表中。
function sum6(x: number, ...items: number[]): number {
items.forEach(element => {
x+= element
});
return x;
}
console.log(sum6(1,2,3,4,5)); // 15
函数重载
如果一个函数接收不同的参数类型和参数数量,并作出不同的响应处理,那么这个函数就是重载函数,关键点在于不同的参数列表。例如我们想实现字符串数值、数字两者之间任意组合的相加。
function add(x: number, y:number):number;
function add(x:number, y:string): number;
function add(x:string, y:number): number;
function add(x:string, y:string): number;
function add(x: number | string, y: number | string): number {
if (isNaN(Number(x)) || isNaN(Number(y))) {
return 0;
}
return Number(x) + Number(y);
}
console.log('add', add('1', 2));
类型断言
类型断言就是手动指定一个值的类型,一般用于可能会有多种类型的选择的时候。使用方式:值 as 类型,或者<类型>值。为了考虑兼容性,建议使用as来做类型断言。
联合类型中使用类型断言
function fun1(x: string | number): void {
console.log((x as string).split('').reverse().join());
}
使用类型断言需要注意:类型断言只能解决编译时的报错,但是无法避免运行时的报错。合理使用断言,避免断言后调用方法或者深层次引用属性,减少不必要的运行时错误。
将一个父类断言为更加具体的子类
当类之间存在继承关系时,可以使用类型断言。
class AError extends Error {
code: number = 999
}
class BError extends Error {
status: number = 200
}
// function isAError(error: Error) {
// if (error instanceof AError) {
// return true;
// }
// return false;
// } // 更推荐
function isAError(error: Error) {
if (typeof (error as AError).code === 'number') {
return true;
}
return false;
}
当接口之间存在继承关系,也可以使用类型断言
interface AError extends Error {
code: number;
}
interface BError extends Error {
status: number;
}
function isAError2(error: Error) {
if (typeof (error as AError).code === 'number') {
return true;
}
return false;
}
将任何一个类型断言为any
any表示任意类型,一旦某个值的类型被断言为any,那么将可以访问任何属性和方法。尽管如此,若非必要,不建议将类型断言为any。
(window as any).foo = 1;
将任意类型any断言为一个具体的类型
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
双重断言
既然任意类型可以被断言为any,然后any也可以被断言为任意类型,那么两个不兼容的类型就可以通过双重断言来实现。
interface Cat {
run(): void;
}
interface Fish {
swim(): void;
}
function testCat(cat: Cat) {
return (cat as any as Fish).swim();
} // 双重断言很容易出现运行时错误,若非必要,不建议使用双重断言
类型断言的限制
类型断言不是说任意类型都可以被断言为其他任意类型的,只有兼容的两个类型之间才可以断言。例如A兼容B,那么A就能被断言为B,同样B也能被断言为A;B兼容A,那么A就能被断言为B,同样B也能被断言为A。说起来有点绕,那么怎么理解兼容呢?
对于联合类型来说,肯定是兼容对应的单一类型的,因此联合类型可以被断言为其中的一种类型;ts是结构类型系统,类型之间的对比只会对比其最终的解构,会忽略他们定义时的关系。如果是类或接口之间存在继承关系,这种继承关系可以是显示的extends,也可以是隐式的(拥有相同属性或方法),那么此时父类是兼容子类的,也就可以断言。
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
// 等价于
interface Animal {
name: string;
}
interface Cat extends Animal {
run(): void;
}
// 那么Animal就兼容Cat,两者就可以进行类型断言
区别
类型断言与类型转换的区别
类型断言只会影响ts编译时的类型,不会实际改变变量的类型,类型断言语句在编译结果中会被删除。而类型转换会实际改变变量类型。
function toBoolean(s: any): boolean {
return s as boolean; // 原值返回
// return Boolean(s); // 返回bool值
}
toBoolean(1)
类型断言与类型声明的区别
我们可以在声明变量时就指定其类型,也可以在声明变量后将其类型断言为某一类型,声明和断言很相似,但是还是存在一些差别。A是否能断言为B,只需要满足A兼容B,或者B兼容A。但是如果想把A赋值给B,那么需要满足B兼容A,也就是说A应该是B的子类,不能把父类的实例直接赋值给子类。
类型声明比类型断言更加严格,应该优先使用类型声明。
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
const animal: Animal = {
name: 'tom',
eat: function (): void {
throw new Error("Function not implemented.");
}
};
// let cat: Cat = animal; // 编译出错
let cat = animal as Cat; // 断言,编译正常
类型断言和泛型的区别
我们可以把函数改造为泛型,在实际调用时指定具体的类型,这样不经可以时间断言一样的效果,而且还可以避免出现any。
function getCache<T>(key: string): T {
return (window as any).cache[key]
}
interface Cat {
name: string,
run(): void
}
let catData = getCache<Cat>('tom');
内置对象
js中有很多的内置对象,也可以直接在ts中使用。
ES内置对象,例如Boolean、Date、RegExp等
let bool: Boolean = new Boolean(1); // true let date:Date = new Date(); let regexp: RegExp = /[ab]/
DOM和BOM的内置对象,例如Document、HTMLElement、Event等
let div:HTMLDivElement = document.getElementsByTagName('div')[0]
TypeScript和Node.js
Node.js不是内置对象的一部分,如果要用ts写Node.js,则需要引入Node.js的声明文件。
npm install @types/node --save-dev
总结
今天的摸鱼时间结束,总结一下今天学到的内容:
- 数组类型可用泛型、接口等方式约束
- 函数的可选参数、默认参数、剩余参数,函数重载
- 联合类型、类、接口、双重断言
- 常见的内置对象