Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript 学习笔记 #58

Open
Nealyang opened this issue Nov 19, 2019 · 0 comments
Open

Typescript 学习笔记 #58

Nealyang opened this issue Nov 19, 2019 · 0 comments
Labels

Comments

@Nealyang
Copy link
Owner

Nealyang commented Nov 19, 2019

typescript

JavaScript 是一门动态弱类型语言。类型思维的缺失

思维方式决定编程习惯,编程习惯奠定工程质量,工程质量划定了能力边界

学习 Typescript 更重要的类型思维的重塑

强类型和弱类型

强类型语言:不允许改变变量的数据类型,除非使用类型转换
弱类型语言:变量可以被赋予不同的数据类型

静态类型和动态类型

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

  • 编译阶段确定属性偏移量
  • 用偏移量访问代替属性名访问
  • 偏移量信息共享

对类型要求严格,立即发现错误,运行时性能良好,自文档化

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

  • 在程序运行时,动态计算属性偏移量
  • 需要额外的空间存储属性名
  • 所有的对象偏移量信息各存一份

对类型要求比较宽松,Bug 可能隐藏数年,运行时性能差,可读性差

任何语言特性都具有两面性

JavaScript 就是动态弱类型语言

![IMAGE](quiver-image-url/69946B0EE18B47D54521F3449D9295EA.jpg =566x280)

编写 Typescript 程序

tsc --init生成配置项

tsc ./src/index.ts用来编译 ts 文件为 js 文件

配置构建工具:webpack

webpack webpack-cli webpack-dev-server

基本数据类型

  • Boolean
  • Number
  • String
  • Array
  • Function
  • Object
  • Symbol
  • undefined
  • null
  • void
  • any
  • never
  • 元祖
  • 枚举
  • 高级类型

类型注解,相当于强类型语言中的类型声明
语法:(变量/函数):type

// 原始类型
let bool: boolean = true
let num: number | undefined | null = 123
let str: string = 'abc'

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

// 元组 特殊的数组,限定数组的类型和个数
let tuple: [number, string] = [0, '1']
// tuple.push(2)
// console.log(tuple) 允许插入,当时访问不到
// tuple[2]

// 函数
let add = (x: number, y: number) => x + y//let add = (x: number, y: number):number => x + y 通常返回值类型是可以省略的,利用 ts 的类型推断能力
let compute: (x: number, y: number) => number
compute = (a, b) => a + b

// 对象 明确指定有哪些属性
let obj: { x: number, y: number } = { x: 1, y: 2 }
obj.x = 3

// symbol 唯一的值
let s1: symbol = Symbol()
let s2 = Symbol()
// console.log(s1 === s2)

// undefined, null 声明了 undefined,就不能被赋值为别的类型,undefined 和 unll 是其他类型的子类型
// tsconfig.js -> strictNullChecks 设置为 false,其他的变量也同样不能被赋值为 undefined、null
let un: undefined = undefined
let nu: null = null
num = undefined
num = null

// void 没有任何返回值的类型
let noReturn = () => {}

// any 不指定一个变量类型,那么就是 any 类型,就可以任意赋值,就跟 JavaScript 没有区别了
let x
x = 1
x = []
x = () => {}

// never 永远不会有返回值的类型 比如死循环、比如函数抛出异常
let error = () => {
   throw new Error('error')
}
let endless = () => {
   while(true) {}
}

枚举类型

将程序中不容易记忆的硬编码,或者未来中可能改变的常量抽取出来。提高可读性

枚举:一组具有名字的常量集合

![IMAGE](quiver-image-url/162732FF760CF5832541CE2229655A33.jpg =278x111)

// 数字枚举
enum Role {
    Reporter = 1,
    Developer,
    Maintainer,
    Owner,
    Guest
}
// console.log(Role.Reporter)
// console.log(Role)
// 枚举其实会被编译成了对象,反向映射的原理

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

// 枚举成员
// Role.Reporter = 0
enum Char {
    // const member
    a,
    b = Char.a,
    c = 1 + 3,
    // computed member
    d = Math.random(),
    e = '123'.length,
    f = 4
}

// 常量枚举 用 const 声明的枚举类型,会在编译阶段被移除。编译后没有任何代码。作用就是当我们不需要一个对象,而需要对象值的时候就可以使用常量枚举
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

interface

约束对象、函数以及类的结构和类型,代码协作的契约

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 = {//传入多余的字段,也可以被通过类型检查。但是如果直接传入给 render 是一个对象字面量的话,ts 就会对额外的字段进行类型检查
/**
* 绕过这种检查的方法除了使用变量,还可以使用对象断言 as + 对象类型。或者在前面加上尖括号。
* 修改接口,添加字符串索引签名 [x:string]:any;
*/
    data: [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B', age: 10}
    ]
}
render(result)

render(<Result>{data: [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B', age: 10}
    ]})
interface StringArray { //用任意的数字去索引 stringArray,都会得到一个 string 。相当于字符串数组
    [index: number]: string
}
let chars: StringArray = ['a', 'b']

interface Names {//字符串索引一个接口
    [x: string]: any;
    // y: number;
    [z: number]: number;//数字索引签名的返回值值一定要是字符串索引签名的返回值值的子类型。
}

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()

函数

// 函数定义
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)


//函数的重载。ts 处理重载的时候会先检查重载的列表。如果匹配的话,就使用这个函数的定义。如果不匹配,就查找下一个函数的定义。
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'))

Ts 的类,引用了 es6 中的类,同时还引入了其他功能的实现

类成员的属性都是实例属性,而不是原型属性。类成员的方法都是实例方法

实例的属性都必须要有初始值,或者在构造函数中被初始化

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' //共有成员 默认修饰符 。 如果在构造函数中增加一个 public 修饰符。这样,这个属性就会变成实例属性,代码更加的简洁
    run() {}
    private pri() {}//私有成员,只能在类的本身被调用,而不能被类的实例或者子类调用 如果在构造函数上加上 private 关键字,那么这个类既不可以被继承,又不可以被实例化
    protected pro() {}//受保护成员 只能在类或者子类中访问,而不能在类的实例中访问 构造函数添加 protected:这个类不能被实例化,只能被继承。相当于声明了一个基类
    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) {//如果在构造函数中增加一个 public 修饰符。这样,这个属性就会变成实例属性,代码更加的简洁
        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 { // this 链式调用
    step1() {
        return this
    }
    step2() {
        return this
    }
}
new Workflow().step1().step2()

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

类和接口

interface Human {//接口只能约束类的共有成员
    name: string; // 接口不能约束类的构造函数
    eat(): void;
}

class Asian implements Human { //类实现接口的时候需要实现接口中定义的所有属性
    constructor(name: string) {
        this.name = name;
    }
    name: string
    eat() {}
    age: number = 0 // 类可以新增自己的属性
    sleep() {}
}

interface Man extends Human { // 接口可以被继承。
    run(): void
}

interface Child {
    cry(): void
}

interface Boy extends Man, Child {}// 甚至可继承多个接口

let boy: Boy = { // 接口的继承可以抽离出可重用的接口,也可以将多个接口合并成一个接口
    name: '',
    run() {},
    eat() {},
    cry() {}
}

class Auto {
    state = 1
    // private state2 = 1 私有
}
interface AutoInterface extends Auto { // 接口可以继承类。相当于接口把类的成员都抽象了出来(不仅仅是公共成员,还有私有成员和受保护成员),也就是只有类的成员结构,而没有具体的实现 
  // 这样,接口中就隐含了 state 属性
}
class C implements AutoInterface {
    state = 1
}
class Bus extends Auto implements AutoInterface { // auto 的子类也可以实现这个接口。这里都不需要手动实现 state 属性

}

![IMAGE](quiver-image-url/992DD4EDE259FBFD737328A5EA17C95F.jpg =539x200)

泛型

不预先确定的数据类型,具体的类型在使用的时候再确定

function log<T>(value: T): T {
    console.log(value);
    return value;
}

// 两种调用方式
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 }) //也可以不传入类型参数,当不指定的时候,value 的值就可以是任意的值

类型约束,需预定义一个接口

interface Length {
    length: number
}
function logAdvance<T extends Length>(value: T): T {
    console.log(value, value.length);
    return value;
}

// 输入的参数不管是什么类型,都必须具有 length 属性
logAdvance([1])
logAdvance('123')
logAdvance({ length: 3 })

好处:

  • 函数和类可以轻松的支持多种类型,增强程序的扩展性
  • 不必写多条函数重载,冗长的联合类型声明,增强代码的可读性
  • 灵活控制类型之间的约束
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant