JS类型-TS扩展类型实例

Js类型有哪些问题? JS的超集TS又有哪些扩展呢?

# 类型安全:强类型与弱类型

强类型 - 语言层面限制实参与形参类型相同

弱类型 - 不限制实参与形参类型

# 类型转换:静态类型与动态类型

静态类型 - 变量声明是类型明确,之后就不允许修改

动态类型 - 运行时才能确定变量类型,并且类型可以变化

var x = 100
x = '100'
console.log(x) // '100'

各语言静态动态强弱示意

# JS自有类型的问题

JS是弱类型且动态类型的语言,几乎没有类型限制,比较随意。这种结果在当下算是问题,不可靠甚至是坑。 但在当时却是优点。

JS在设计之初,处理的问题并没有现在这么复杂,这样的随意和灵活非常容易上手,写完就能用,却是是很大优势。 而且,JS是脚本语言,没有编译环节,所以也就没有必要去做静态编译阶段要做的类型检查了。

到了现在,js面对如此庞大的项目,万物皆可js的当下,这些优势就都变成了短板。

# 弱类型的问题

let obj = {}

console.log(obj.x.y())

上面这么写完全没问题,而且必须要到运行时执行到才会报错。

function sum(x, y) {
    return x + y
}

console.log(1, 2) // 3
console.log(1, '2') // '12'

上面这个函数的求和原有意义就很不明朗了。

以上如果是强类型语言,在语法检查或编译时就会发现问题,而不会留到运行出错时造成问题才发现了。

# 强类型优势

  1. 错误更早暴露
  2. 编码更智能,编码会更准确
  3. 重构更可靠
  4. 减少必要的类型判断

# Flow

Flow是JS的类型检查器,他通过让我们在开发时,增加对变量和参数的类型注解来检查代码。

function sum(a: number, b: number) {
    return a + b
}

console.log(sum(1, '2')) // flow检查不通过 

最终这些注解会在babel等工具的处理去除,并不会影响上线代码。

# TS

TS是JS的超集;TS是在原有JS基础上扩展的,扩展了类型系统和ES6的新特性,最终再会编译为JS; 目前主流项目均已转入TS开发,第三方库包括我们实际的项目都在逐步切换。

可以说,TS已经是主流。

# 原始类型

// 原始数据类型
const x: string = '1212'
const y: number = 12 // NaN  Infinity
const z: boolean = true // false
const a: void = undefined // 常用于函数返回值
const b: null = null
const c: undefined = undefined
const d: symbol = Symbol()

# 对象类型

// 这里object并不单指对象,而是除了原始对象外的其他类型
const obj1: object = [] // {},function(){}

// 下面这种才是定义普通对象的方式,但更常用的是interface的形式
const obj2: {} = {}
const obj3: { name: string } = {name: 'jack'}

# 数组类型

// 两种方式均可
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]

# 元组类型

这是一种特殊数据结构,他是一个明确元素数量和类型的数组,内部的元素类型不一定相同

const tuple: [number, string] = [11, 'abc']
const tuple2: [number, string] = [11, 'abc', true] // 这会报错

const tuple3 = Object.entries({
    x: 1, y: 2
}) // 返回值就是类型为 [string,number] 的元组

# 枚举类型

// 枚举值可以不指定,默认从0累计,也可以指定字符串
enum Status {
    Read = 0,
    Unread = 1,
    Delete = 2,
}

const post = {
    name: 'xxxx',
    status: Status.Read
}

枚举类型不一样的地方在于,他最终会侵入我们编写的代码,我们可以看看最终编译的结果。

var Status;
(function (Status) {
    Status[Status["Read"] = 0] = "Read";
    Status[Status["Unread"] = 1] = "Unread";
    Status[Status["Delete"] = 2] = "Delete";
})(Status || (Status = {}));

/**
 * {
    "0": "Read",
    "1": "Unread",
    "2": "Delete",
    "Read": 0,
    "Unread": 1,
    "Delete": 2
}
 */
var post = {
    name: 'xxxx',
    status: Status.Read
};

可以看出,他是一个双向键值对象。他既可以通过键获取值,也可以通过值获取键。

# 函数类型

function sum(a: number, b: number): number {
    return a + b
}

sum(1, 2)
sum(1, 2, 3) // 报错参数个数需要一致

// 不定参数长度
function sum(a: number, b: number, ...rest: number[]): number {
    return a + b + rest.length
}

sum(1, 2, 3) 
const fun: (a: number, b: number) => number = function (a, b) {
    return a + b
}

# 任意类型

// 这里的参数可以接受任意类型
function stringify(val: any): string {
    return JSON.stringify(val)
}

stringify(1)
stringify('11')
stringify(true)

let x: any = 1

x = true
x.y = 12

为了兼容老代码需要使用any,但轻易不要用,因为这和js代码一样,没有了约束,就没有了类型系统但优势。

# 类型推断/类型断言

ts可以根据上下文做类型推断

let x = 19;
x = '212' // 报错,因为推断为x为number类型

类型断言是说,ts在某些情况下无法推断具体类型,但开发者知道是什么类型,这时候我们就可以通过类型断言来指定类型。

const nums = [1, 2, 3, 4, 5, 5, 6];

// 推断返回值为number|undefined,因为ts认为可能找不到
const finder = nums.find(x => x === 1);

// 作为开发者我们知道会有确定结果,所以可以这样写:
const fin = finder as number
const fin2 = <number>finder

需要注意的是,这里并不是类型转换,而仅仅是编译时用的,编译完成后不会再存在。

# 接口 Interface

用来约定对象中成员类型;仅用来做类型约束,不会被编译到生产环境

interface User {
    name: string
    age: number
    addr?: string // 可选
    readonly birth: string // 只读属性
}

function updateUser(user: User) {
    user.name = 'jason'
    user.age = 19
    user.birth = '1999/09' // 报错,只读属性
}

interface Cache {
    [prop: string]: string // 动态属性,任意类型
}

# 类 Class

类,用来描述一类具体事物的抽象特征;TS中增强了类的语法;

class Animal {
    public name: string // 默认public修饰
    private age = 18 // 私有属性
    protected gender = true //受保护,只允许子类访问
    readonly birth: string // 只读属性

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    say(msg: string): void {
        console.log(msg)
    }
}

const cat = new Animal('meme', 19)
console.log(cat.gender) // 报错访问不到,受保护

class Cat extends Animal {
    constructor(name: string, age: number) {
        super(name, age);
        console.log(this.gender) // 可以访问
    }
}
// 抽象某种单一能力,更易于组合使用
interface Eat {
    eat(msg: string): void
}

interface Run {
    go(msg: string): void
}

class Cat implements Eat, Run {
    eat() {
        console.log('cat eat')
    }

    go() {
        console.log('cat go')
    }
}

class Dog implements Eat, Run {
    eat() {
        console.log('dog eat')
    }

    go() {
        console.log('dog go')
    }
}
//抽象类 只能继承,不能实例化
abstract class Animal {
    eat(food: string): void {
        console.log('eat', food)
    }

    abstract run(addr: string): void
}

class Dog extends Animal {
    run(addr: string): void {
        console.log('run', addr)
    }

}

const dog = new Dog()
dog.eat('meat')
dog.run('shangshai') // 均可直接使用

# 泛型

定义函数接口或者类时,没有指定具体类型,而是在使用时指定类型。

这样可以最大程度复用代码;

function createNumArr(len: number, val: number): Array<number> {
    return Array(len).fill(val);
}

function createStrArr(len: number, val: string): Array<string> {
    return Array(len).fill(val);
}

以上两个函数作用非常类似,初始化指定长度的数组,并用给定值填充;但却不得不写两个函数

这就需要泛型出场了。

function createArr<T>(len: number, val: T): Array<T> {
    return Array(len).fill(val);
}

// 这样就可以创建任意类型的数组了
createArr(10, 'abc')
createArr(10, true)
createArr(10, 100)
最近更新
01
echarts扇形模拟镜头焦距与可视化角度示意图
03-10
02
vite插件钩子
03-02
03
vite的依赖预构建
02-13
更多文章>