ts学习-基础-极客

200 阅读11分钟

强类型语言,与弱类型语言

在强类型语言中,当一个对象从调用函数传递到被调用函数时,其类型必须与被调用函数中声明的类型兼容

A() {
    B(x)
}

B(y) {
    // y可以被赋值x,程序运行良好
}

强类型语言:不允许改变变量的数据类型,除非进行强制类型转换 若类型语言:变量可以被赋予不同的数据类型

动态类型语言与静态类型语言

静态类型语言: 在编译阶段确定所有变量的类型 动态类型语言: 在执行阶段确定所有变量的类型

js 代码

class C {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

function add (a, b) {
    return a.x + a.y + b.x + b.y
}

在看这段代码时你完全不知道变量的类型只有在程序运行时才可以知道

c++ 代码

class C {
    public: 
        int x;
        int y;
}

int add (C a, C b) {
    return a.x + a.y + b.x + b.y
}

可以清楚的看到所有变量的类型一定是整形,

静态类型语言:

1.对类型极度严格 2.立即发现错误 3.运行时性能良好 4.自文档化

动态类型语言

1.对类型非常宽松 2.bug可以隐藏数月 3.运行时性能差 4.可读性差

动态类型语言的支持者认为

  • 性能可以改善(v8 引擎),语言的灵活性更重要

  • 隐藏的错误可以通过单元测试发现

  • 文档可以通过工具生成

基本类型

vsC-2.jpg

ES6 数据类型

  • boolean
  • number
  • string
  • array
  • function
  • object
  • symbol
  • undefined
  • null

ts数据类型

  • boolean
  • number
  • string
  • array
  • function
  • object
  • symbol
  • undefined
  • null
  • void
  • any
  • never
  • 元组
  • 枚举
  • 高级类型

类型注解

作用: 相当于强类型语言的类型声明 语法:(变量/函数): type

// 原始类型
let bool: boolean = true
let num: number = true
let str: string = true

// 数组
let arr1: number[] = [1,2,3]
let arr2: Array<number | string> = [1,2,3,4]

// 元组 (特殊数组,限定数组类型与个数)
let tuple: [number, string] = [0, '1']
// 越界问题
tuple.push('3');
// ts允许元组加入元素但并不允许访问,不建议使用

// 函数
let add = (x: number, y: number) => x + y
let com: (x: number, y: number) => number
com = (a, b) => a + b;

// 对象
let obj:object = {x: 1, y: 2}
let obj:{x: number, y: number} = {x: 1, y: 2}

// symbol

let s1: symbol = Symbol()
let s2: Symbol()
// s1,s2不相等

// undefined, null
let un: undefined = undefined;
let nu: null = null;

// 在官方文档中 undefined 和 null 是任何类型的子类型
// ts.config.json中 strictNullChecks设置为false,

// void
let noReturn = () => {};

// any
let x
// 可以给x赋值任何值

// never 永远不会有返回值的类型

let error = () => {
    throw new Error('error')
}
let endless = () => {
    while(true) {}
}

枚举类型

vsC-3.jpg


function init(role) {
    if (role === 1 || role === 2){
        // do
    } else if (role === 3) {
        // do
    } else if (role === 4){
        // do
    } else {
        // do
    }
}

问题:

  1. 可读性差:很难记住数字的含义
  2. 可维护性差:硬编码,牵一发动全身

枚举:一组有名字的常量集合 例如 张三: 12312312312

// 数字枚举 取值从0开始 后面的会递增, 可以给定默认值,后面的递增
enum Role {
    Reporter = 1,
    Developer,
    Maintainer,
    Owner,
    Guest
}
// console.log(Role.Developer, '测试')  --1
// console.log(Role, '测试')
// 1: "Reporter"
// 2: "Developer"
// 3: "Maintainer"
// 4: "Owner"
// 5: "Guest"
// Developer: 2
// Guest: 5
// Maintainer: 3
// Owner: 4
// Reporter: 1
// 枚举的原理---反向映射

// 字符串枚举 --不可进行反向映射
enum Message {
    Success = '恭喜你,成功了',
    Fail = '抱歉,失败了'
}

// 异构枚举
enum Answer {
    N,
    Y = 'Yes'
}

// 枚举成员
// Role.Reporter = 0
// 枚举类型是只读类型不可修改
enum Char {
    // 枚举成员的分类
    // const member --常量枚举
    // 三种情况 1.无初始值 2.对已有枚举成员的引用3.常量的表达式
    a, 
    b = Char.a,
    c = 1 + 3,
    // computed member -- 非常量表达式(不会再编译阶段被计算,保留到执行阶段)注意:要有初始值
    d = Math.random(),
    e = '123'.length,
    f = 4
}

// 常量枚举  当我们不需要对象而需要对象的值的时候就可以使用常量枚举
const enum Month {
    Jan,
    Feb,
    Mar,
    Apr = Month.Mar + 1,
    // May = () => 5
}
let month = [Month.Jan, Month.Feb, Month.Mar]


// 枚举类型 -- 两种不同类型的枚举不可进行比较
enum E { a, b }
enum F { a = 0, b = 1 }
enum G { a = 'apple', b = 'banana' }

let e: E = 3
let f: F = 3
// console.log(e === f)

let e1: E.a = 3
let e2: E.b = 3
let e3: E.a = 3
// console.log(e1 === e2)
// console.log(e1 === e3)

let g1: G = G.a
let g2: G.a = G.a

类型的接口

vsC-4.jpg

interface List {
    readonly id: number;
    name: string;
    // [x: string]: any;
    age?: number;
}
interface Result {
    data: List[]
}
function render(result: Result) {
    result.data.forEach((value) => {
        console.log(value.id, value.name)
        if (value.age) {
            console.log(value.age)
        }
        // value.id++
    })
}
let result = {
    data: [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B', age: 10}
    ]
}
render(result)

// ts允许传多余的参数,但直接放在参数中会报错例如
// render({data: [
//     {id: 1, name: 'A', sex: 'male'},
//     {id: 2, name: 'B', age: 10}
// ]}  as Result)
// 解决方式
//     1. 赋值给变量
//     2. 类型断言(2种方式)
//     3. 字符串索引签名  // [x: string]: any;

//     render({data: [
//         {id: 1, name: 'A', sex: 'male'},
//         {id: 2, name: 'B', age: 10}
//     ]}  as Result)

//     render(<Result>{data: [
//         {id: 1, name: 'A', sex: 'male'},
//         {id: 2, name: 'B', age: 10}
//     ]})
// 可选属性 age?: number;
// 只读属性 readonly id: number; 不允许修改

interface StringArray {
    [index: number]: string
}
let chars: StringArray = ['a', 'b']

interface Names {
    [x: string]: any;
    // y: number;
    [z: number]: number;
}
// 两种签名可以混用(两种属性要兼容)

// let add: (x: number, y: number) => number
// interface Add {
//     (x: number, y: number): number
// }
type Add = (x: number, y: number) => number
let add: Add = (a: number, b: number) => a + b

// 混合类型接口
interface Lib {
    (): void;  
    version: string;
    doSomething(): void;
}

function getLib() {
    let lib = (() => {}) as Lib
    lib.version = '1.0.0'
    lib.doSomething = () => {}
    return lib;
}
let lib1 = getLib()
lib1()
let lib2 = getLib()
lib2.doSomething()

函数

vsC-5.jpg

// 函数定义
function add1(x: number, y: number) {
    return x + y
}

let add2: (x: number, y: number) => number

type add3 = (x: number, y: number) => number

interface add4 {
    (x: number, y: number): number
}

// add1(1, 2, 3)

function add5(x: number, y?: number) {
    return y ? x + y : x
}
add5(1)
// 可选参数必须在必选参数之后


// 可以给定默认值
function add6(x: number, y = 0, z: number, q = 1) {
    return x + y + z + q
}
// 必选参数不可省略
add6(1, undefined, 3)

// 剩余参数
function add7(x: number, ...rest: number[]) {
    return x + rest.reduce((pre, cur) => pre + cur);
}
add7(1, 2, 3, 4, 5)

// 函数重载,先定义一系列名称相同的声明
function add8(...rest: number[]): number;
function add8(...rest: string[]): string;
function add8(...rest: any[]) {
    let first = rest[0];
    if (typeof first === 'number') {
        return rest.reduce((pre, cur) => pre + cur);
    }
    if (typeof first === 'string') {
        return rest.join('');
    }
}
console.log(add8(1, 2))
console.log(add8('a', 'b', 'c'))

vsC-6.jpg

// 抽象类 关键字 abstract // 无法创建实例只能被继承
abstract class Animal {
    eat() {
        console.log('eat')
    }
    abstract sleep(): void
}
// let animal = new Animal()

class Dog extends Animal {
    constructor(name: string) {
        super()
        this.name = name
        this.pri()
    }
    public name: string = 'dog'
    run() {}
    private pri() {}
    protected pro() {}
    readonly legs: number = 4
    static food: string = 'bones'
    sleep() {
        console.log('Dog sleep')
    }
}
// console.log(Dog.prototype)
let dog = new Dog('wangwang')
// console.log(dog)
// dog.pri()
// dog.pro()
console.log(Dog.food)
dog.eat()

class Husky extends Dog {
    constructor(name: string, public color: string) {
        super(name)
        this.color = color
        // this.pri()
        this.pro()
    }
    // color: string
}
console.log(Husky.food)

class Cat extends Animal {
    sleep() {
        console.log('Cat sleep')
    }
}
let cat = new Cat()

let animals: Animal[] = [dog, cat]
animals.forEach(i => {
    i.sleep()
})

class Workflow {
    step1() {
        return this
    }
    step2() {
        return this
    }
}
new Workflow().step1().step2()

class MyFlow extends Workflow {
    next() {
        return this
    }
}
new MyFlow().next().step1().next().step2()

泛型

vsC-7.jpg 泛型: 不预先知道的数据类型,具体的类型再使用的时候才能确定

使用泛型的好处

  1. 函数和类可以轻松的支持多种类型,增强程序的扩展性
  2. 不必写多条函数重载,冗长的联合类型声明,增强代码可读性
  3. 灵活控制类型之间的约束
function log<T>(value: T): T {
   console.log(value);
   return value;
}

// 2种调用方式
log<string[]>(['a', ',b', 'c'])
log(['a', ',b', 'c'])

// type Log = <T>(value: T) => T
// let myLog: Log = log

// 
// interface Log<T> {
//     (value: T): T
// }
// let myLog: Log<number> = log
// myLog(1)

class Log<T> {
   run(value: T) {
       console.log(value)
       return value
   }
}
let log1 = new Log<number>()
log1.run(1)
let log2 = new Log()
log2.run({ a: 1 })

interface Length {
   length: number
}
// 类型约束
function logAdvance<T extends Length>(value: T): T {
   console.log(value, value.length);
   return value;
}
logAdvance([1])
logAdvance('123')
logAdvance({ length: 3 })

类型检查机制

类型检查机制: TypeScript编译器在做检查时,所秉承的一些原则,以及表现出的一些行为。

作用:辅助开发,提高开发效率

  • 类型推断
  • 类型兼容性
  • 类型保护

类型推断

vsC-9.jpg

let a = 1;
let b = [1, null, 'a']
let c = {x: 1, y: 'a'}

let d = (x = 1) => x + 1

window.onkeydown = (event) => {
    // console.log(event.button)
}

// 类型断言
interface Foo {
    bar: number
}
// let foo = {} as Foo
// let foo = <Foo>{}
let foo: Foo = {
    bar: 1
}
// foo.bar = 1

类型兼容

vsC-10.jpg

当一个类型y可以被赋值给另一个类型x时,我们就可以说类型x兼容类型y

x兼容y:x(目标类型) = y(源类型)

结构之间兼容:成员少的兼容成员多的

函数之间兼容:参数多的兼容参数少的

/*
 * X(目标类型) = Y(源类型),X 兼容 Y
 */

let s: string = 'a'
// str = null
// 源类型必须具备目标类型的必要属性就可以赋值(成员少的兼容成员多的)
// 接口兼容性
interface X {
    a: any;
    b: any;
}
interface Y {
    a: any;
    b: any;
    c: any;
}
let x: X = {a: 1, b: 2}
let y: Y = {a: 1, b: 2, c: 3}
x = y
// y = x      y不能赋值给x

// 函数兼容性
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
    return handler
}

// 1)参数个数
let handler1 = (a: number) => {}
hof(handler1)
let handler2 = (a: number, b: number, c: number) => {}
// hof(handler2)  目标函数有两个参数


// 可选参数和剩余参数
let a = (p1: number, p2: number) => {}
let b = (p1?: number, p2?: number) => {} 
let c = (...args: number[]) => {}
a = b // 固定参数可以兼容可选参数和剩余参数的
a = c // 固定参数可以兼容可选参数和剩余参数的
// b = a // 可选参数不可以兼容固定参数和剩余参数的
// b = c // 可选参数不可以兼容固定参数和剩余参数的
c = a // 剩余参数可以兼容固定参数和可选参数
c = b // 剩余参数可以兼容固定参数和可选参数

// 2)参数类型  必须要匹配
let handler3 = (a: string) => {}
// hof(handler3)

interface Point3D {
    x: number;
    y: number;
    z: number;
}
interface Point2D {
    x: number;
    y: number;
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d  // 成员个数多的兼容成员个数少的
// p2d = p23

// 3) 返回值类型
let f = () => ({name: 'Alice'})
let g = () => ({name: 'Alice', location: 'Beijing'})
f = g // 成员个数少的兼容成员个数多的
// g = f

// 函数重载
function overload(a: number, b: number): number
function overload(a: string, b: string): string
function overload(a: any, b: any): any {}
// function overload(a: any): any {}
// function overload(a: any, b: any, c: any): any {}
// function overload(a: any, b: any) {}

// 枚举兼容性
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
let fruit: Fruit.Apple = 1
let no: number = Fruit.Apple
// let color: Color.Red = Fruit.Apple

// 类兼容性
class A {
    constructor(p: number, q: number) {}
    id: number = 1
    private name: string = ''
}
class B {
    static s = 1
    constructor(p: number) {}
    id: number = 2
    private name: string = ''
}
class C extends A {}
let aa = new A(1, 2)
let bb = new B(1)
// aa = bb
// bb = aa
let cc = new C(1, 2)
aa = cc
cc = aa

// 泛型兼容性
interface Empty<T> {
    // value: T
}
let obj1: Empty<number> = {};
let obj2: Empty<string> = {};
obj1 = obj2

let log1 = <T>(x: T): T => {
    console.log('x')
    return x
}
let log2 = <U>(y: U): U => {
    console.log('y')
    return y
}
log1 = log2

类型保护

vsC-11.jpg

TypeScript 能够在特定的区块中保证变量属于某种确定的类型

可以在此区块中放心地引用此类型的属性,或者调用此类型的方法

enum Type { Strong, Week }

class Java {
    helloJava() {
        console.log('Hello Java')
    }
    java: any
}

class JavaScript {
    helloJavaScript() {
        console.log('Hello JavaScript')
    }
    js: any
}

function isJava(lang: Java | JavaScript): lang is Java {
    return (lang as Java).helloJava !== undefined
}

function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new JavaScript();
    
    if (isJava(lang)) {
        lang.helloJava();
    } else {
        lang.helloJavaScript();
    }

    // if ((lang as Java).helloJava) {
    //     (lang as Java).helloJava();
    // } else {
    //     (lang as JavaScript).helloJavaScript();
    // }

    // instanceof
    // if (lang instanceof Java) {
    //     lang.helloJava()
    //     // lang.helloJavaScript()
    // } else {
    //     lang.helloJavaScript()
    // }

    // in
    // if ('java' in lang) {
    //     lang.helloJava()
    // } else {
    //     lang.helloJavaScript()
    // }

    // typeof
    // if (typeof x === 'string') {
    //     console.log(x.length)
    // } else {
    //     console.log(x.toFixed(2))
    // }

    return lang;
}

getLanguage(Type.Week, 1)

高级类型

交叉类型与联合类型

vsC-12.jpg

interface DogInterface {
    run(): void
}
interface CatInterface {
    jump(): void
}

// 交叉类型 用 & 链接 取所有类型并集
let pet: DogInterface & CatInterface = {
    run() {},
    jump() {}
}

// 联合类型  不仅可以联合类型还可以约定值
let a: number | string = 1
let b: 'a' | 'b' | 'c'
let c: 1 | 2 | 3

class Dog implements DogInterface {
    run() {}
    eat() {}
}
class Cat  implements CatInterface {
    jump() {}
    eat() {}
}
enum Master { Boy, Girl }
function getPet(master: Master) {
    let pet = master === Master.Boy ? new Dog() : new Cat();
    // pet.run()
    // pet.jump()
    pet.eat()
    return pet
}

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
type Shape = Square | Rectangle | Circle
// 根据kind 创建不同的类型保护区块
function area(s: Shape) {
    switch (s.kind) {
        case "square":
            return s.size * s.size;
        case "rectangle":
            return s.height * s.width;
        case 'circle':
            return Math.PI * s.radius ** 2
        default:
            return ((e: never) => {throw new Error(e)})(s)
    }
}
console.log(area({kind: 'circle', radius: 1}))

索引类型

vsC-13.jpg

let obj = {
    a: 1,
    b: 2,
    c: 3
}

// function getValues(obj: any, keys: string[]) {
//     return keys.map(key => obj[key])
// }

// 索引类型
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
    return keys.map(key => obj[key])
}
console.log(getValues(obj, ['a', 'b']))
// console.log(getValues(obj, ['d', 'e']))

// 索引类型的查询操作符
// keyof T 类型T的所有公共属性的字面量的联合类型
interface Obj {
    a: number;
    b: string;
}
let key: keyof Obj
// key的类型变为a、b 的联合类型  "a" | "b"

// 索引访问操作符
// T[K]
let value: Obj['a']

// 泛型变量可以通过集成某个类型获得某些属性
// T extends U

映射类型

vsC-14.jpg

Partial 将类型的属性变成可选

类似于浅比较,只对第一层数据进行可选变化

type Partial<T> = {
    [P in typeof T]?: T[P]
}

type PartialObj = Partial<Obj>

Required 将类型的属性变为必选

type Partial<T> = {
    [P in typeof T]-?: T[P]
}
// 其中 -?是代表移除?这个标识。

Pick 从某个类型中挑出来一些属性

type Pick<T, K extends typeof T> = {
    [P in K]: T[P];
}

type PickObj = Pick<Obj, 'a' | 'b'>

Record 可以获得根据K中所有可能值来设置key 以及 value的类型

type Record<K extends keyof any, T> = {
    [P in K]: T;
}

Mutable 将类型的属性变得可修改

type Mutable<T> = {
    -readonly [P in keyof T]: T[P];
}

Readonly 类型的属性变成只读

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}

ReturnType 用来得到一个函数的返回值类型

type ReturnType<T extends (...args: any[]) => any) = T extends (
...args: any[]
) => infer R ? R : any

条件类型

vsC-15.jpg

// T extends U ? X : Y

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";
type T1 = TypeName<string>
type T2 = TypeName<string[]>

// 分布式条件类型
// (A | B) extends U ? X : Y
// (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]>

// 类型过滤
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<"a" | "b" | "c", "a" | "e">
// Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
// never | "b" | "c"
// "b" | "c"

type NotNull<T> = Diff<T, null | undefined>
type T5 = NotNull<string | number | undefined | null>

// Exclude<T, U> // 官方实现 Diff
// NonNullable<T> // 官方实现 NotNull

// Extract<T, U> // 与Exclude相反,类型抽取
type T6 = Extract<"a" | "b" | "c", "a" | "e">

// ReturnType<T> // 获取函数返回值的类型
type T8 = ReturnType<() => string>