typescript

375 阅读11分钟

全局安装ts

cnpm install -g typescript

编译ts转换成js

编写test.ts

// test.ts
function test(name: string) {
  return 'hello' + name;
}
test('aa')

cd到 test.ts同一路径下,执行命令

tsc test.ts

得到 test.js

function test(name) {
    return 'hello' + name;
}
test('aa');

数据类型

原始数据类型

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. BigInt
  6. String
  7. Symbol
let isDone: boolean = false

// 接下来来到 number,注意 es6 还支持2进制和8进制,让我们来感受下

let age: number = 10
let binaryNumber: number = 0b1111

// 之后是字符串,注意es6新增的模版字符串也是没有问题的
let firstName: string = 'viking'
let message: string = `Hello, ${firstName}, age is ${age}`

// 还有就是两个奇葩兄弟两,undefined 和 null
let u: undefined = undefined
let n: null = null

// 注意 undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
let num: number = undefined

Array 和 Tuple(元组)

//最简单的方法是使用「类型 + 方括号」来表示数组:
let arrOfNumbers: number[] = [1, 2, 3, 4]
//数组的项中不允许出现其他的类型:
//数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
arrOfNumbers.push(3)
arrOfNumbers.push('abc')

// 元祖的表示和数组非常类似,只不过它将类型写在了里面 这就对每一项起到了限定的作用
let user: [string, number] = ['viking', 20]
//但是当我们写少一项 就会报错 同样写多一项也会有问题
user = ['molly', 20, true]
// 顺序不一样,也会报错
let user: [string, number] = [viking, 20]

对象

一般用interface

interface 接口

// 我们定义了一个接口 Person
interface Person {
  name: string;
  age: number;
}
// 接着定义了一个变量 viking,它的类型是 Person。这样,我们就约束了 viking 的形状必须和接口 Person 一致。
let viking: Person ={
  name: 'viking',
  age: 20
}

//有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
    name: string;
    age?: number;
}
let viking: Person = {
    name: 'Viking'
}

//接下来还有只读属性,有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性

interface Person {
  readonly id: number;
  name: string;
  age?: number;
}
viking.id = 9527

多余的属性

interface test {
  name: string
  age: number
  // [key: string] : boolean | number | string
  [key: string]: any
}

const f: test = {
  name: 'a',
  age: 10,
  other: '',
  testaa: 'aa',
  arr: [
    {
      age: 'aa'
    }
  ]
}
console.log('f', f)

函数

// 来到我们的第一个例子,约定输入,约定输出
function add(x: number, y: number): number {
  return x + y
}
// 可选参数
function add(x: number, y: number, z?: number): number {
  if (typeof z === 'number') {
    return x + y + z
  } else {
    return x + y
  }
}

// 函数本身的类型
const add2: (x: number, y: number, z?:number) => number = add

// interface 描述函数类型
const sum = (x: number, y: number) => {
  return x + y
}
interface ISum {
  (x: number, y: number): number
}
const sum2: ISum = sum

类型推论,联合类型 和 类型断言

联合类型

// 我们只需要用中竖线来分割两个
let numberOrString: number | string 
// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
numberOrString.length
numberOrString.toString()

类型断言as

// 这里我们可以用 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
  }
}

类型守卫

// typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
function getLength2(input: string | number): number {
  if (typeof input === 'string') {
    return input.length
  } else {
    return input.toString().length
  }
}

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name
  }
  run() {
    return `${this.name} is running`
  }
}
const snake = new Animal('lily')

// 继承的特性
class Dog extends Animal {
  bark() {
    return `${this.name} is barking`
  }
}

const xiaobao = new Dog('xiaobao')
console.log(xiaobao.run())
console.log(xiaobao.bark())

// 这里我们重写构造函数,注意在子类的构造函数中,必须使用 super 调用父类的方法,要不就会报错。
class Cat extends Animal {
  constructor(name) {
    super(name)
    console.log(this.name)
  }
  run() {
    return 'Meow, ' + super.run()
  }
}
const maomao = new Cat('maomao')
console.log(maomao.run())

类成员的访问修饰符

  1. public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  2. private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  3. protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

js类,es6类,ts类对比

leetcode 146

js类

        /**
         * @param {number} capacity
         */
        var LRUCache = function (capacity) {
            this.capacity = capacity;
            this.map = new Map();
        };

        /** 
         * @param {number} key
         * @return {number}
         */
        LRUCache.prototype.get = function (key) {
            let value = this.map.get(key)
            if (value === undefined) {
                return -1;
            }
            this.map.delete(key);
            this.map.set(key, value);
            return value;
        };

        /** 
         * @param {number} key 
         * @param {number} value
         * @return {void}
         */
        LRUCache.prototype.put = function (key, value) {
            if (this.map.has(key)) {
                this.map.delete(key);
            }
            this.map.set(key, value);
            if (this.map.size > this.capacity) {
                let keyIterator = this.map.keys();
                this.map.delete(keyIterator.next().value);
            }
        };

es6

        class LRUCache {
            constructor(capacity) {
                this.capacity = capacity
                this.map = new Map()
            }
            get(key) {
                let value = this.map.get(key)
                if (value === undefined) {
                    return -1;
                }
                this.map.delete(key);
                this.map.set(key, value);
                return value;
            }
            put(key, value) {
                if (this.map.has(key)) {
                    this.map.delete(key);
                }
                this.map.set(key, value);
                if (this.map.size > this.capacity) {
                    let keyIterator = this.map.keys();
                    this.map.delete(keyIterator.next().value);
                }
            }
        }

ts

class LRUCache {
  capacity: number
  map: Map<number, number>
  constructor (capacity: number) {
    this.capacity = capacity
    this.map = new Map()
  }
  get (key: number): number {
    let value = this.map.get(key)
    if (value === undefined) {
      return -1
    }
    this.map.delete(key)
    this.map.set(key, value)
    return value
  }
  put (key: number, value: number) {
    if (this.map.has(key)) {
      this.map.delete(key)
    }
    this.map.set(key, value)
    if (this.map.size > this.capacity) {
      let keyIterator = this.map.keys()
      this.map.delete(keyIterator.next().value)
    }
  }
}

运行

var cache = new LRUCache(2 /* 缓存容量 */)

cache.put(1, 1)
cache.put(2, 2)
console.log(cache.get(1)) // 返回  1
cache.put(3, 3) // 该操作会使得关键字 2 作废
console.log(cache.get(2)) // 返回 -1 (未找到)
cache.put(4, 4) // 该操作会使得关键字 1 作废
console.log(cache.get(1)) // 返回 -1 (未找到)
console.log(cache.get(3)) // 返回  3
console.log(cache.get(4)) // 返回  4

类与接口


interface Radio {
  switchRadio(trigger: boolean): void;
}
class Car implements Radio {
  switchRadio(trigger) {
    return 123
  }
}
class Cellphone implements Radio {
  switchRadio() {
  }
}

interface Battery {
  checkBatteryStatus(): void;
}

// 要实现多个接口,我们只需要中间用 逗号 隔开即可。
class Cellphone implements Radio, Battery {
  switchRadio() {
  }
  checkBatteryStatus() {

  }
}

改写成extend


interface Radio {
  switchRadio(trigger: boolean): void;
}
interface Battery {
  checkBatteryStatus(): void;
}

interface RadioWithBattery extends Radio {
  checkBatteryStatus(): void;
}

class Car implements Radio {
  switchRadio(trigger) {
    return 123
  }
}

// 要实现多个接口,我们只需要中间用 逗号 隔开即可。
class Cellphone implements RadioWithBattery {
  switchRadio(trigger:boolean) {
  }
  checkBatteryStatus() {

  }
}

枚举 Enums

// 数字枚举,一个数字枚举可以用 enum 这个关键词来定义,我们定义一系列的方向,然后这里面的值,枚举成员会被赋值为从 0 开始递增的数字,
enum Direction {
  Up,
  Down,
  Left,
  Right,
}
console.log(Direction.Up)

// 还有一个神奇的点是这个枚举还做了反向映射
console.log(Direction[0])

// 字符串枚举
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
  console.log('go up!')
}

泛型 Generics

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

基本使用

// any
// function echo(arg:any) {
//   return arg
// }
// const result = echo(123)

// 一个值
function echo<T>(arg:T):T{
  return arg;
}

const result1 = echo('a')
const result2 = echo(1)
const result3 = echo(true)

// 泛型也可以传入多个值
function swap<T,U>(tuple:[T,U]):[U,T]{
  return [tuple[1],tuple[0]]
}

const res1 = swap(['aa',123])
console.log(res1[1])
console.log(res1[0])

const res2 = swap([123,'aa'])
console.log(res1[1])
console.log(res1[0])

泛型约束

// 原始报错
泛型 T 不一定包含属性 length,我们可以给他传入任意类型,当然有些不包括 length 属性,那样就会报错
// function echoWithArr<T>(arg:T):T{
//   console.log(arg.length)
//   return arg;
// }

// 数组约束
// function echoWithArr<T>(arg:T[]):T[]{
//   console.log(arg.length)
//   return arg;
// }
// var arr1 = echoWithArr([1,2,3])
// var arr2 = echoWithArr('123') //报错

// 接口约束
// interface IWithLength {
//   length:number;
// }
// function echoWithArr<T extends IWithLength>(arg:T):T{
//   console.log(arg.length)
//   return arg;
// }

// const str = echoWithArr('str')
// const obj1 = echoWithArr({length:10})
// const obj2 = echoWithArr({length:10,width:10})
// const obj3 = echoWithArr({width:10}) // 报错
// const arr2 = echoWithArr([1,2,3])

泛型与类

// class Queue{
//   private data = [];
//   push(item:number){
//     return this.data.push(item)
//   }
//   pop(){
//     return this.data.shift()
//   }
// }

// const queue = new Queue();
// queue.push(1)
// queue.push('str')  //报错
> 在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,当然,当数据被弹出队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据,但是那么在使用的过程中,就会出现我们无法捕捉到的错误,

// console.log(queue.pop().toFixed()) //没有检查出来错误
// console.log(queue.pop().toFixed()) //没有检查出来错误


class Queue<T>{
  private data = [];
  push(item:T){
    return this.data.push(item)
  }
  pop():T{
    return this.data.shift()
  }
}

const queue1 = new Queue<number>();
queue1.push(1)
console.log(queue1.pop().toFixed())

const queue2 = new Queue<string>();
queue2.push('str')
// console.log(queue2.pop().toFixed())  //检查出来错误

泛型与接口

interface KeyPair<T,U>{
  key:T,
  value:U
}

let kp1: KeyPair<number,string> = {key:1,value:'string'}
let kp2: KeyPair<string,number> = {key:'str',value:1}
let kp3: KeyPair<string,number[]> = {key:'str',value:[1,2,3]}

类型别名

类型别名,就是给类型起一个别名,让它可以更方便的被重用。

let sum: (x: number, y: number) => number
const result = sum(1,2)
type PlusType = (x: number, y: number) => number
let sum2: PlusType

// 支持联合类型
type StrOrNumber = string | number
let result2: StrOrNumber = '123'
result2 = 123

// 字符串字面量
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Directions = 'Up'

交叉类型

interface IName  {
  name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: 'hello', age: 12}

类的装饰器Decorators

  1. 装饰器为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式
  2. 装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上
  3. 只能应用于类

初始化

npm init -y //package.json
tsc --init  //tsconfig.ts

npm install ts-node -D
npm install typescript --save

修改package.json

  "scripts": {
    "dev": "ts-node ./src/index.ts"
  },

修改tsconfig.ts,支持装饰器

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true, 

基本

只在类定义的时候执行一次,实例化的时候不再执行

function testDecorator(constructor:any){
    console.log('constructor', constructor) //constructor [Function: Test]
}

@testDecorator
class Test{}

const test1 = new Test();
const test2 = new Test();

调用一个方法

function testDecorator(constructor:any){
  constructor.prototype.getName = () => {
    console.log('dell')
  }
}

@testDecorator
class Test{}

const test1 = new Test();
(test1 as any).getName()

多个装饰器的先后顺序,

先引用,后执行

function testDecorator(constructor:any){
  console.log('Decorator')
}

function testDecorator1(constructor:any){
  console.log('Decorator1')
}


@testDecorator
@testDecorator1
class Test{}

const test1 = new Test();

是否调用装饰器

function testDecorator(flag:boolean){
  if(flag){
    return function(constructor:any){
      constructor.prototype.getName = () => {
        console.log('dell')
      }
    }
  }else{
    return function(constructor:any){}
  }
}

@testDecorator(false)
class Test{}

const test1 = new Test();
(test1 as any).getName()

方法的装饰器

三个参数

target

普通方法,target 对应的是类的prototype 静态方法, target 对应的是类的构造函数

key

key 对应是它对应装饰的方法

descriptor

descriptor 对应的是它控制这个方法是否可以重写,返回值

function getNameDecorator (
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
  console.log('target', target) //target Test { getName: [Function] }
  console.log('key', key) //key getName
  console.log('descriptor', descriptor) //descriptor {
  value: [Function],
  writable: true,
  enumerable: true,
  configurable: true
}
}

class Test {
  name: string
  constructor (name: string) {
    this.name = name
  }
  @getNameDecorator
  getName () {
    return this.name
  }
}

const test1 = new Test('dell')
console.log(test1.getName())

不可以重写

function getNameDecorator(target:any,key:string,descriptor:PropertyDescriptor){
  descriptor.writable = false; // 默认true
}

class Test {
  name:string;
  constructor(name:string){
    this.name = name;
  }
  @getNameDecorator
  getName (){
    return this.name;
  }
}

const test = new Test('dell');
test.getName = () => {
  return '123'
}
console.log(test.getName()) //报错

修改返回值

function getNameDecorator(target:any,key:string,descriptor:PropertyDescriptor){

  // descriptor.value = 'aa'  // 错误写法
  descriptor.value = function(){
    return 'aa'
  }
}

class Test {
  name:string;
  constructor(name:string){
    this.name = name;
  }
  @getNameDecorator
  getName (){
    return this.name;
  }
}

const test = new Test('dell');
console.log(test.getName()  // aa

装饰器使用demo

不使用装饰器

报错

const userInfo:any = undefined;

class Test {
  getName (){
    return userInfo.name;
  }
  getAge(){
    return userInfo.age;
  }
}
const test = new Test();
test.getName()

使用try,catch处理报错

const userInfo:any = undefined;

class Test {
  getName (){
    try{
      return userInfo.name;
    }catch(e){
      console.log('userInfo.name不存在')
    }
  }
  getAge(){
    try{
      return userInfo.age;
    }catch(e){
      console.log('userInfo.age不存在')
    }
  }
}

const test = new Test()
test.getName()

使用装饰器

基本使用 存在

const userInfo:any = undefined;

// function catchError(target:any,key:string,descriptor:PropertyDescriptor){
//   const fn = descriptor.value;
//   descriptor.value = function(){
//     try{
//       fn()
//     }catch(e){
//       console.log('userInfo存在问题')
//     }
//   }
// }

// class Test {
//   @catchError
//   getName (){
//     return userInfo.name;
//   }
//   @catchError
//   getAge(){
//     return userInfo.age;
//   }
// }

// const test = new Test()
// test.getName()
// test.getAge()

访问器装饰器

function getNameDecorator(target:any,key:string,descriptor:PropertyDescriptor){

  descriptor.writable = false;
}

class Test {
  private _name:string;
  constructor(name:string){
    this._name = name;
  }
  get name(){
    return this._name;
  }
  @getNameDecorator
  set name(name:string){
     this._name = name;
  }
}

// 1. 是否可重写
const test = new Test('dell');
test.name = 'dell111'
console.log(test.name)  //报错

属性装饰器

禁止修改属性

function nameDecorator(target:any,key:string):any{
  const descriptor:PropertyDescriptor ={
    writable:false
  }
  return descriptor
}

class Test{
  @nameDecorator
  name = 'Dell'
}

const test = new Test();
test.name = 'dell lee'
console.log(test.name);

重新赋值

//修改的并不是实例上的name,而是原型上的name
function nameDecorator(target:any,key:string):any{
  target[key] = 'lee'
}

//name 是放在实例上的
class Test{
  @nameDecorator
  name = 'Dell'
}

const test = new Test();
console.log((test as any).__proto__.name);

参数装饰器

三个参数

原型,方法名,参数所在位置

function paramDecorator(target:any,method:string,paramIndex:number){
  console.log('target',target) //target Test { getInfo: [Function] }
  console.log('method',method) //method getInfo
  console.log('paramIndex',paramIndex) //paramIndex 1
}
class Test{
  getInfo(name:string,@paramDecorator age:number){
    console.log(name,age)
  }
}

const test = new Test();
test.getInfo('dell',30)

reflect-metadata

元数据

元数据是用来描述数据的数据 什么是 元数据

(她)年纪已经不轻,三十岁上下,个子高挑,身材匀称,黑黑的眉毛,红红的脸蛋--一句话,不是姑娘,而是果冻,她那样活跃,吵吵嚷嚷,不停地哼着小俄罗斯的抒情歌曲,高声大笑,动不动就发出一连串响亮的笑声:哈,哈,哈!

这段话里提供了这样几个信息:年龄(三十岁上下)、身高(个子高挑)、相貌(身材匀称,黑黑的眉毛,红红的脸蛋)、性格(活跃,吵吵嚷嚷,不停地哼着小俄罗斯的抒情歌曲,高声大笑)。有了这些信息,我们就可以大致想像出瓦莲卡是个什么样的人。推而广之,只要提供这几类的信息,我们也可以推测出其他人的样子。

这个例子中的"年龄"、"身高"、"相貌"、"性格",就是元数据,因为它们是用来描述具体数据/信息的数据/信息。

参考

import 'reflect-metadata'

class User {
  @Reflect.metadata('data','test')
  @Reflect.metadata('data1','test1')
  getName(){}
}

class Teacher extends User {}

console.log(Reflect.getOwnMetadataKeys(User.prototype,'getName'))
console.log(Reflect.getOwnMetadataKeys(Teacher.prototype,'getName'))

Reflect.defineMetadata

对元数据进行命令式的定义

  1. 为类定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target)
  1. 为类的成员(属性和方法)定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)
import 'Reflect-metadata'

class People {
  static firstName: string = 'li'
  lastName: string
  constructor (lastName: string) {
    this.lastName = lastName
  }
  getFullName (): string {
    return People.firstName + this.lastName
  }
}

// 定义
Reflect.defineMetadata('name', 'People11', People)
Reflect.defineMetadata('name', 'firstName11', People, 'firstName')
Reflect.defineMetadata('name', 'lastName11', People, 'lastName')
Reflect.defineMetadata('name', 'getFullName11', People.prototype, 'getFullName')

Reflect.defineMetadata

获取元数据信息

  1. 获取类(构造函数)上的元数据
Reflect.getMetadata(metadataKey, target);
  1. 获取静态成员或实例成员的元数据
Reflect.getMetadata(metadataKey, target, propertyKey);
// 获取
console.log(Reflect.getMetadata('name', People)) //People
console.log(Reflect.getMetadata('name', People, 'firstName')) //firstName
console.log(Reflect.getMetadata('name', People.prototype, 'lastName')) //lastName
console.log(Reflect.getMetadata('name', People.prototype, 'getFullName')) //getFullName

@Reflect.metadata

Reflect.metadata 当做装饰器来使用,直接装饰类或类的属性

@Reflect.metadata(metadataKey, metadataValue)

使用这样的装饰器语法,就相当于在类或类的属性上添加了元数据的键名和键值

@Reflect.metadata('name', 'people11')
class People {
  @Reflect.metadata('name', 'firstName11')
  static firstName: string = 'li'
  @Reflect.metadata('name', 'lastName11')
  lastName: string
  constructor (lastName: string) {
    this.lastName = lastName
  }
  @Reflect.metadata('name', 'getFullName11')
  getFullName (): string {
    return People.firstName + this.lastName
  }
}

console.log(Reflect.getMetadata('name', People)) //people11
console.log(Reflect.getMetadata('name', People, 'firstName')) //firstName11
console.log(Reflect.getMetadata('name', People.prototype, 'lastName')) //lastName11
console.log(Reflect.getMetadata('name', People.prototype, 'getFullName')) //getFullName

tsconfig.json 配置文件

{
  "compilerOptions": {
    "target": "ES2015",  // 最高支持的js版本
    "outDir": "./build", // 打包转成js后的文件夹
    "rootDir": "./src",  // 要被转译的文件夹
   }
}