Type Script学习笔记:基础
原始数据类型
boolean
使用构造函数Boolean创建的对象不是布尔值,而是一个Boolean对象,如下的代码无法通过编译。
let createdByNewBoolean: boolean = new Boolean(1);
// Type 'Boolean' is not assignable to type 'boolean'.
// 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
直接调用Boolean可以返回一个boolean对象。
let createdByBoolean: boolean = Boolean(1);
在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样,不再赘述。
数值
定义数值类型的时候可以使用0x,0b,0o语法,0b和0o会被编译成十进制。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
编译得到
var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;
实际上在本机器测试的时候会自动换为ES6语法,不会修改对应成十进制。
string
注意模板字符串的用法,必须用反引号`包裹。
let myName: string = 'Tom';
let myAge: number = 25;
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
console.log(sentence)
空值
js中没有空值(Void)的概念,TS中可以用void表示没有任何返回值的函数。
function alertName(): void {
alert('My name is Tom');
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null(只在 –strictNullChecks 未指定时):
let unusable: void = undefined;
Null 和 Undefined
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量
任意值
any类型允许被赋值为任意类型
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
在任意值上访问任何属性,调用任何方法都是允许的,对其进行的任何操作返回的值都是任意值
未声明类型的变量
不声明的话默认是any
联合类型
取值可以是多种类型中的一种
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {
return something.length;
}
// 报错 index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
function getString(something: string | number): string {
return something.toString();
}
接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
赋值的时候,变量的形状必须和接口的形状保持一致
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,如下的代码会报错。
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number;
[propName: string]: string | number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性,下述代码会报错
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
数组
最简单的方法是使用“类型 + 方括号”来表示数组,项中不允许出现其他类型,方法也会被限制。
let fibonacci: number[] = [1, 1, 2, 3, 5];
也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
也可以用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
其表示索引的类型是数字时值的类型是数字。
一般不用这个方法来表示普通数组,常用于表示类数组。
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
在上面的例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性。
事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function sum() {
let args: IArguments = arguments;
}
一个比较常见的做法是,用 any 表示数组中允许出现任意类型:
let list: any[] = ['google', 25, { website: 'http://google.com' }];
函数
// 函数声明(Function Declaration)
function sum(x: number, y: number): number {
return x + y;
}
// 函数表达式(Function Expression) 类似于闭包
let mySum = function (x: number, y: number): number {
return x + y;
};
上述例子中的函数表达式写法其实省略了mySum的类型,其实际类型是(x: number, y: number) => number
可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
用?定义可选的参数,可选参数后不允许再添加必须参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
添加了默认值的参数会被识别为可选参数,此时不受可选参数后不允许再添加必须参数的限制了。
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
可以用...rest的方式获取函数中的剩余参数
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
类型断言
用 值 as 类型 或 <类型>值的方式,一般用前者,后者方括号格式本身用于表示ReactNode和泛型,容易混淆。
类型断言能够将一个联合类型断言为其中一个类型,从而调用类型独有的属性和方法。
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
如果不加以检查地使用类型断言会导致运行时错误。
类型断言能够将一个父类断言为更加具体的子类。
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
虽然上述例子使用error instance of ApiError更恰当,但是如果是接口就不能使用instance of了。
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
类型断言能够将类型断言为any,从而实现往对象中添加新属性。
(window as any).foo = 1;
还能将any断言为具体类型从而提高代码的可维护性。一旦可以确定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();
断言的实际限制
若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A
TS作为类型结构系统,其实际上比较的是最终的结构而不是定义时的关系。
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
let tom: Cat = {
name: 'Tom',
run: () => { console.log('run') }
};
let animal: Animal = tom;
使用双重断言可以实现强制的类型转换,但是一般来说没意义。
类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除
所以类型断言不是类型转换,它不会真的影响到变量的类型。
若要进行类型转换,需要直接调用类型转换的方法
function toBoolean(something: any): boolean {
return Boolean(something);
}
toBoolean(1);
// 返回值为 true
类型的声明是比类型断言更加严格的,只要A兼容B那么两者之间可以互相进行断言,但是只能将B赋值给A而不能将A赋值给B,不能将父类的实例赋值给类型为子类的变量。
可以用泛型实现更加规范的返回值约束。
function getCacheData<T>(key: string): T {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData<Cat>('tom');
tom.run();
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西,因此我们需要使用declare var来定义其类型。
declare var jQuery: (selector: string) => any;
jQuery('#foo');
通常我们会将声明的语句放进单独的以.d.ts为文件名的文件,会自动解析
可以直接用在社区中获取想要的的声明文件,可以使用@types统一管理
npm install @types/jquery --save-dev
目前的npm在安装包本体如果有声明文件则会直接安装,不需要单独下载@types包了
如果我们需要书写声明文件的话方法比较复杂,需要的时候再学吧 https://ts.xcatliu.com/basics/declaration-files.html#%E4%B9%A6%E5%86%99%E5%A3%B0%E6%98%8E%E6%96%87%E4%BB%B6
内置对象
ECMAScript的内置对象有Boolean、Error、Date、RegExp 等。
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
可以参考https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
DOM 和 BOM 提供的内置对象有:Document、HTMLElement、Event、NodeList 等。
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
TypeScript 核心库的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在 TypeScript 中的。
Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
npm install @types/node --save-dev