TypeScript
TypeScript官网 https://www.typescriptlang.org/zh/
TypeScript中文官网 https://www.tslang.cn/samples/index.html
编程语言的类型分为动态类型语言(Dynamically Typed Language)和静态类型语言(Statically Typed Language)。
动态类型语言
是在运行期间才会进行类型检查的语言。类型对于变量,属性,方法以及方法的返回类型都是可有可无的,在给变量赋值时才决定它的类型,之后还可以赋值不同类型的值,即使是基本类型。例如JavaScript,python,Ruby。
静态类型语言
是在编译阶段进行类型检查。写程序时要声明变量的数据类型。例如C,C++,Java。
TypeScript
就是把不看重类型的动态语言变成关注类型的动态语言。是Javascript的超集,提供从es6到es10,甚至是esnext的语法支持。兼容各种浏览器、系统,完全开源。
为什么使用TypeScript?
- 程序更容易理解
对于确定函数或者方法输入输出的参数类型,外部条件等,动态类型语言需要手动调试等过程,例如debugger、console。有了TypeScript就很容易确定以上内容。 - 效率更高
在不同的代码块和定义中跳转,代码自动补全。 - 更少的错误
编译期间就能够发现大部分错误,避免一些常见错误(property 'xxxx' undefined) - 非常好的包容性
完全兼容JavaScript,第三方库可以单独编写类型文件。
但会增加一定程度上的学习成本,短期内增加了一些开发成本。所以TS适用于一些中大型长期开发维护的项目。
TypeScript安装
安装node,或确定已安装的node的版本至少为10.0.0以上。
全局安装TypeScript,运行npm install -g typescript。安装完成之后输入tsc -v可查看版本号。(tsc 代表 TypeScript 编译器,只要编译器运行,它将在项目文件夹中查找名为tsconfig.json 的文件。)
基础类型
原始数据类型(primitive values):
Boolean、Null、Undefined、Number、BigInt(es6新增)、String、Symbo
let isDone:boolean = false
let age:number = 10
let firstName:string = 'vivid'
let message:string = `Hello,${firstName}`
let u:undefined = undefined
let n:null = null
//undefined和null是所有类型的子类型
let num:number = undefined
注:null and undefined
null表示"没有对象",即该处不应该有值,转为数值时为0。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
1、作为函数的参数,表示该函数的参数不是对象
2、作为对象原型链的终点
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义,转为数值时为NaN。
1、变量被声明了,但没有赋值时,就等于undefined
2、调用函数时,应该提供的参数没有提供,该参数等于undefined
3、对象没有赋值的属性,该属性的值为undefined
4、函数没有返回值时,默认返回undefined
console.log(typeof (null))// object
console.log(typeof (undefined))// undefined
Number(null)//0
Number(undefined)// NaN
null == undefined// true
null === undefined// false
//变量被声明,但没有赋值
var a
console.log(a)// undefined
//函数定义了形参,但没有传递实参
function fn(a) {
console.log(a)// undefined
}
fn()
//引用对象中不存在的属性
console.log(Object.a); // undefined
//函数没有返回值
var a = fn()
console.log(a)// undefined
Any和unkonw
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any
类型来标记这些变量:
let notSure:any = 4
notSure = 'maybe a string'
notSure = true
notSure = {Hello: () => {}}
译者: any 和 unknown 的最大区别是, unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) , 这导致 any 基本上就是放弃了任何类型检查。
对照于 any,unknown 是类型安全的。 任何值都可以赋给 unknown,但是当没有类型断言或基于控制流的类型细化时 unknown 不可以赋值给其它类型,除了它自己和 any 外。 同样地,在 unknown 没有被断言或细化到一个确切类型之前,是不允许在其上进行任何操作的。
//任何值都可以赋给 unknown
let uncertain:unknown = 5
uncertain = 'is a string'
uncertain = {Hello: () => {}}
// unknown 不可以赋值给其它类型,除了它自己和 any 外
notSure = uncertain
let isDone:boolean = false
isDone = uncertain//Type 'unknown' is not assignable to type 'boolean'.
//简单应用
const isFalsy = (value: unknown) => (value === 0 ? false : !value);
console.log(isFalsy(''))//true
Array和Tuple
Array 有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:
//数组的项不允许出现其他类型
let arrOfNumber: number[] = [1,2,3]
//数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
arrOfNumbers.push(3)
arrOfNumbers.push('abc')
第二种方式是使用数组泛型,Array<元素类型>:
let arrOfString: Array<String> = ['abc','def','g']
Tuple(元组)
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 其本质还是一个数组,可以使用一些数组的方法。
let user:[string,number] = ['jack',29]
//数组中添加的项只能是定义的类型
user.push('man')
user.push(true)//Error, 布尔不是(string | number)类型
Interfaces 接口
对对象的类型进行描述,也被成为“鸭子类型”。只在ts中作静态检查。
interface Person{
name: string,
age: Number
}
let rose: Person = {
name: 'rose',
age: 20
}
function userUser(infor:Person){
console.log('user')
}
userUser({name: 'rose',age: 20})
可选属性:接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
//age是可选或者不选的
interface Person{
name: string,
age?: Number
}
let rose: Person = {
name: 'rose',
}
只读属性:一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性。
interface Person{
readonly id: number,
name: string,
age?: Number
}
let rose: Person = {
id: 1,
name: 'rose',
}
rose.id = 2//Error
//TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似
let readonlyArr: ReadonlyArray<Number> = [1,2,3,4]
readonlyArr[0] = 6//Error,Index signature in type 'readonly Number[]' only permits reading
readonly vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用readonly。
Function 函数
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。
//z为可选参数,可选参数后面不能添加确定参数
function add(x:number,y:number,z?:number):number{
if (typeof z === 'number') {
return x+y+z
}else{
return x+y
}
}
add(1,2)
add(1,2,3)
//let add = (x: number, y: number): number => { return x + y; };
let add2: (x:number,y:number,z?:number) => number = add// ts中冒号后面都是在声明类型
//另一种写法
interface sum {
(x:number,y:number,z?:number): number
}
let add3:sum = add
Union Types 联合类型、Type assertions 类型断言
//我们只需要用中竖线来分割两个
let utype: number | string
//这里我们可以用 as 关键字,告诉typescript 编译器,
//你没法判断我的代码,但是我本人很清楚,这里我就把它看作是一个 string,你可以给他用 string 的方法。
function getLength(input:string|number):number{
const str = input as string
if (str.length){
return str.length
}else{
const number = input as number
return number.toString().length
}
}
//type guard类型守卫
function getLength2(input:string|number):number{
if (typeof input === 'string'){
return input.length
}else{
return input.toString().length
}
}
Class 类
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class Animal {
name: string;
constructor(name){
this.name = name
}
run(){
return `${this.name} is running`
}
private eat(){
return `${this.name} is eating`
}
}
const snake = new Animal('snake')
console.log(snake.run())
console.log(snake.eat())// Error
class Dog extends Animal{
dark(){
return `${this.name} is barking`
}
}
const xiaobei = new Dog('xiaobei')
console.log(xiaobei.run())
console.log(xiaobei.dark())
class Cat extends Animal{
static categories = ['mammal']
constructor(name){
super(name)
console.log(this.name)
}
run(){
return 'Meow,'+super.run()
}
}
const maomao = new Cat('maomao')
console.log(maomao.run())
console.log(Cat.categories)
Class 类和Interface接口
同类之间有共同的特性,可以把这些特性提取为接口,可以使用implement来实现接口。
//类和接口
// class Car {
// switchRadio(trigger:boolean){}
// }
// class CellPhone {
// switchRadio(trigger:boolean){}
// }
interface Radio {
switchRadio(trigger:boolean): void;
}
interface Barrery {
checkBarrery(trigger:boolean): void;
}
interface RadioBattery extends Barrery {
checkBarrery(trigger:boolean): void;
}
class Car implements Radio{
switchRadio(trigger:boolean){}
}
class CellPhone implements Radio,Barrery{
switchRadio(trigger:boolean){}
checkBarrery(trigger:boolean){}
}
Enum 枚举
一般会使用const来定义一些常量,但有些取值是再范围内的一系列常量,例如红黄蓝、上下左右,这时候就可以用枚举来定义。
// 数字枚举
// 没有定义初值时,初始值为0,其余的成员会从 0开始自动增长
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up);// 0
console.log(Direction[0]);// Up
//若定义一个数字枚举,Up 的初始值为10,自动增长Down为11,Left为12,Right为13
enum Direction {
Up = 10,
Down,
Left,
Right
}
console.log(Direction.Up);// 10
console.log(Direction[11]);// Down
// 字符串枚举
// 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。字符串枚举没有自增长的行为。
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
// 常量枚举
// 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举
const enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
Generics 泛型
function echo(arg:any):any{
return arg
}
const result = echo(123)
上面的例子使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
泛型在定义函数类的时候,可以不先指定类型,而是可以在使用的时候指定类型。
//给函数echo类型变量T。 T帮助我们捕获用户传入的类型(比如:number)
function echo<T>(arg: T): T{
return arg;
}
const result = echo('123')
function swap <T,U> (tuple:[T,U]): [U,T]{
return [tuple[1],tuple[0]]
}
const res = swap([1,'123'])
泛型约束
function echoWithArray<T>(arg:T):T{
console.log(arg.length) // Error: T doesn't have .length
return arg
}
相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。
function echoWithArray<T>(arg:T[]):T[]{
return arg
}
//更好的解决方法
function echoWithArray<T extends IWithLength>(arg:T):T{
console.log(arg.length)
return arg
}
interface IWithLength{
length: number
}
const res2 = echoWithArray([1,2,3])
const res3 = echoWithArray({length: 10})
泛型在类和接口中的使用
class Queue<T> {
private data = [];
push(item:T){
return this.data.push(item);
}
pop():T{
return this.data.shift();
}
}
const queue = new Queue<number>();
queue.push(1)
console.log(queue.pop().toFixed())
interface keyPair<T,U>{
key: T,
value: U
}
let kp1: keyPair<number,string> = {key: 1,value: '1'}
type alias类型别名
类型别名(type aliases)为一个类型创建一个新的名字。有些时候类型别名与接口(interfaces)很相似,但是类型别名可以用于声明原始类型(primitives),联合类型(unions),元组类型(tuples),还有其它一些你必须要手写的类型。
type alias与interface的区别
// 1、声明对象和函数类型的语法不一样
interface A {
x: number;
y: string;
}
interface B {
(m:number,n:string): void
}
type A = {
x: number;
y: string;
}
type B = (m:number,n:string): => void
//2、类型别名(type alias)可以用来声明一些接口(interface)无法声明的其他类型
// 声明已有类型(即取别名)
type A = number;
// 字面量类型
type B = 'foo';
// 元组类型
type C = [number, string];
// 联合类型
type D = number | boolean;
type Directions = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
const toWhere:Directions = 'DOWN'
// 交叉类型
type E = A & D;
interface IName {
name: string
}
type IPerson = IName & {age:number}
const amy: IPerson = {name: 'amy',age: 20}
// 3、接口(interface)可以通过合并不同的声明进行不断扩展
interface A {
x: number;
}
interface A {
y: string;
}
// 经过上面两次声明后 A 的类型为: { x: number; y: number; }
// 4、接口(interface)通过 extends 关键字来扩展一个类型(这个类型可以是 interface, 也可以是 type alias),类型别名(type alias)则通过交叉类型来实现扩展
interface Super {
a: string;
}
interface Sub extends Super {
b: number;
}
// type alias
type Super = {
a: string;
};
type Sub = Super & {
b: number
};
声明文件
在引用第三方库的时候,需要声明文件。在xxx.d.ts文件中声明
declare var JQuery: (selector: string) => any
//ts中的JQuery('#foo')就不会报错
但我们不可能一个个去定义,所以在TypeScript 2.0以上的版本,获取类型声明文件只需要使用npm。例如获取JQuery的声明文件
npm install --save @types/jquery
TypeScript查询对应包的类型文件可在https://microsoft.github.io/TypeSearch/查询。有些在安装的时候,直接提供定义文件和源代码,安装包里有xxx.d.ts就说明有定义文件。
内置对象
根据标准在全局作用域(global)上存在的对象,这里的标准是指ECMAScript和其他的环境(eg:DOM的标准)。在每个ts项目中会自动的加载这些对象。
//ECMAScript标准提供的内置对象
const date = new Date();
date.getDate();
const reg = /[a-z]/
reg.test('ab')
// build-in Object
Math.random()
// DOM and BOM,提供的内置对象有Document、HTMLElement、Event、NodeList 等
let body: HTMLElement = document.body;
let allLis:NodeListOf<HTMLLIElement> = document.querySelectorAll('li');
allLis.keys();
document.addEventListener('click',(e)=>{
e.preventDefault();
})
Utility Types
Utility Types是ts内置的实用类型,用于类型转换。常用的类型有:Partial, Required, Readonly, Record<K,T>,Pick<T,K>,Omit<T,K>,Exclude<T,U>等。
文档链接:https://www.typescriptlang.org/docs/handbook/utility-types.html
//Partial<T>
//将泛型传入的T中所有属性转换为可选属性,返回的类型可以是T的任意子集。
interface UPerson {
name: string,
age: number,
sex: boolean
}
const vivi = {name: 'vivi',age: 22,sex:true}
//相当于type IPartial = {name?: string; age?: number;sex?:boolean}
type IPartial = Partial<UPerson>
const vivi2:IPartial = {name: 'vivi'}
//Required<T>
//将泛型传入的T中所有属性转换为必须属性,和Partial类型相反。
type IRequired = Required<UPerson>
const vivi3:IRequired = {age: 20}// Error. Property 'name' is missing in type '{ age: number; }' but required in type 'Required<UPerson>'
//Pick<T,K>
//通过传入的泛型T中选择一组属性K(字符串字面值或字符串字面值的联合)来构造类型。
//相当于type IPick = {name: string;sex: boolean;}
type IPick = Pick<UPerson,'name' | 'sex'>
const vivi4:IPick = {name: 'vivi',sex: true}
//Omit<T,K>
//通过传入的泛型T中选择一组属性K并删除其他属性,和Pick相反。
type Iomit = Omit<UPerson,'name'>
const vivi5:IPartial = {age: 20,sex: true}