js语言特性与typescript详述

强类型与弱类型(类型安全)/静态类型与动态类型(类型检查)

强类型与弱类型用来区分编程语言的类型安全。强类型是在语言(语法)层面限制函数的实参类型必须与形参类型相同。弱类型不会限制实参的类型。(强类型语言中不允许有任何隐式类型转换,弱类型语言允许任何隐式转换)。

静态类型语言:一个变量在声明的时候,它的类型就是明确的,声明过后类型不允许被修改。

动态类型语言:在运行阶段才能明确变量类型,变量类型可以随时发生变化。在动态类型语言中,变量是没有类型的,而变量存放的值是有类型的。

为什么js没有被设计成强类型+静态类型的语言?

早期js应用简单,js是一个脚本语言,不需要进行编译就可以在运行环境中运行,设计者并没有预见到现在的情形。

弱类型的问题

const obj={
}

obj.foo() // TypeError: obj.foo is not a function

写的时候没有问题,语法正确,但是在运行时就会报错。如果这段代码时异步的,在测试阶段没有检测出来,就会给系统带来隐患。

而如果是强类型的语言,在语法上就会直接报错,不会等到运行阶段。

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

console.log(sum(1,'100')) // 1100

如上,如果两个参数传入的非约定值,就会产生意料之外的结果,函数功能发生改变。

强类型的优势

错误会更早的暴露,不用最后在运行时才会表现;

代码更加智能,编码更加准确;(编辑器中的智能提示)比如,写一个render函数,传入dom元素,此时编辑器不会弹出dom元素的属性值,因为编辑器并不知道:

function render(element){
    element.className='container'
    element.inerHtnl='xxxx' // 这里属性名拼错了,但不会有提示
}

重构更加牢靠,比如:在重构时发现属性名不规范,想要重改,就需要很谨慎的处理,一旦有错误,就会造成系统的崩溃。而如果是强类型语言的话,在编译阶段就会报出错误,就会定位到所有使用到这个成员的地方。

const obj={
    aaa:()=>{} //命名不规范
}

强类型语言会减少不必要的类型判断。

Flow

flow是js的类型检查器,会为函数添加类型注解

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

安装与配置

yarn add flow --dev
yarn flow init // 初始化
yarn add flow-remove-types --dev 
yarn flow-remove-types [输入目录] -d [输出目录] // 删除代码中的flow检查输出到输出目录

使用上述命令,就能把有flow检查的文件转化为没有flow检查的文件,比如:

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

console.log(sum('100',1))

转化后

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

console.log(sum('100',1))

或者使用babel来进行上述转化:

yarn add @babel/core // babel核心文件
yarn add @babel/cli  // 直接在命令行使用bebel
yarn add @babel/preset-flow // 包含转换flow注解的插件

安装过后在项目根目录新建配置文件.babelrc

{
    "presets":["@babel/preset-flow"]
}

之后,在命令行运行

yarn babel [输入] -d [输出]

同样可以得到去flow注解后的代码:

function sum(a, b) {
  return a + b;
}

console.log(sum('100', 1));

flow开发工具插件

如果用上述手动敲命令行并不方便,我们可以安装开发工具插件,在vscode中安装扩展flow language support这个插件

flow中数组类型

// @flow
const arr1:Array<number> =[1] // 全部由数字组成的数组
const arr2:number[]=[1,2,3]

// 固定长度数组-元组
const arr3:[string,number]=['1',2] // 
//const arr3:[string,number]=[1,'2'] // flow报错 

对象类型

// @flow
/* const obj1:{foo:string,bar:number}={
    foo:'a',
} */ // 不写全属性flow会报错

const obj1:{foo:string,bar:number}={
    foo:'a',
    bar:1
}
// 对象中固定值的类型
const obj2:{[string]:number}={
    foo:1,
    // bar:'dddd' // 报错
}

flow函数类型

// @flow
function foo(callback:(string,number)=>void){
    callback('a',100)
}

foo(function(a,b){console.log(`${a} is ${b}`)})

特殊类型

// @flow
// 字面量类型
const a:'foo'='foo'
// const b:'foo'='foo1' // 报错,只能传'foo'
// 联合类型
const type:'success'|'warning'|'error'='error'
const b:string|number='aaa'

// maybe类型
const gender:?number=undefined
const gender1:?number=null
const gender2:?number=1

任意类型

// @flow
// mixed类型 强类型 所有类型
// string|boolean|number|...
function passMixed(value:mixed){
    value.substr(1) // 报错
    value*value
}

// any类型 弱类型 尽量不要用any类型
// string|boolean|number|...
function passAny(value:any){
    value.substr(1) // 不会报错
    value*value
}

TypeScript

ts是js的超集(扩展集),在js的原有基础上多了很多特性,也有更强大的类型系统来完成开发工作,开发过后将编译成js代码。

配置

yarn add typescript
yarn tsc --init // 初始化配置文件

标准库

进入到tsconfig.json中,target为标准库的语言,lib为补充的标准库。

显示中文语言:(不推荐,难以搜索错误)

tsc --locale zh-CN

Object类型

Object类型并不单指对象,还有函数和数组。对象更倾向于用接口类型去写。

元组类型(Turple)

明确元素数量以及以及每个元素类型的数组

const turple:[number,string]=[11,'sdsd',2] // 超出数量报错
const turple2:[number,string]=['22','sdsd'] // 元素类型错误也报错
const turple3:[number,string]=[22,'sdsd'] 

const [age,name]=turple3

export {}

枚举类型

enum postStatus{
    draft=0,
    unpublish=1,
    published=2
}
// 使用
const post={
    id:'dsu2',
    status:postStatus.draft
}

常量枚举,如果用上述写法定义枚举类型,每个枚举类型可以通过下标拿到,比如:
postStatus[0]就是draft。使用常量枚举,让下标不能拿到枚举值:

const enum postStatus{
    draft=0,
    unpublish=1,
    published=2
}

postStatus[0] // 报错

隐式类型断言

let age=10
age='xx' // 报错:已经默认把age推断为number类型

let foo; // any:之后可以当作动态类型
foo=1
foo='sss'

类型断言

const nums=[10,20,30]
const res=nums.find(num=>num<20)

const square=res*res // ts不能直接判断出res是一个number类型

// 此时就要断言告诉ts res就是number类型的
// 使用as
const num1=res as number
const square1=num1*num1
// 使用<Type> 不推荐,因为在jsx会产生冲突
const num2= <number>res
const square2=num2*num2

接口

可以抽象理解为规范或是“契约”,它只是用来约定对象的结构,在实际运行阶段并没有意义。

interface Post{
    title:string
    subtitle?:string // 可选成员
    id:number
    readonly summary:string // 只读成员
}

const hello:Post={
    title:'hello',
    id:1,
    summary:'sdsdsad'
}

hello.summary='sss' // 报错,只读成员在声明后不能被修改


interface Cache{
    [key:string]:string // 动态成员
}
const cache:Cache={
    name:'sdsd'
}
cache.foo='bar' // 可以添加任何成员,但必须以string作为键值

类用来描述一类具体事物的抽象特征。ts增强了class的相关语法。

在ts中要明确声明类所拥有的属性,而不是通过动态添加。

class Person{
    constructor(name:string,age:number){
        this.name=name // 报错
        this.age=age
    }
}

在constructor前要声明类中的成员类型,并要在构造函数里赋予初始值

class Person{
    name:string
    age:number

    constructor(name:string,age:number){
        this.name=name // 通过
        this.age // 报错,必须有初始值
    }
}

类的访问修饰符

class Person{
    name:string  // 默认就是public
    private age:number
    protected gender:boolean

    constructor(name:string,age:number){
        this.name=name // 通过
        this.age=age
        this.gender=true
    }

    sayHi(msg:string){
        console.log(msg+this.age)
    }
}

const tom=new Person('tom',14)
console.log(tom.name) 
console.log(tom.age) // 报错!私有成员只能通过成员函数访问
console.log(tom.gender) // 报错!保护成员只能在类和其子类中访问

class Student extends Person{
    constructor(name:string,age:number){
        super(name,age)
        console.log(this.gender) // 可以访问
    }
}

constructor默认为public,如果是private的,就要使用静态函数来创建该类的实例。如:

class Student extends Person{
    private constructor(name:string,age:number){
        super(name,age)
        console.log(this.gender) // 可以访问
    }
    static create(name:string,age:number){
        return new Student(name,age)
    }
}

const lili=new Student('lili',10) // 报错,类“Student”的构造函数是私有的,仅可在类声明中访问
const lili2=Student.create('lili2',10) // 通过

类与接口

interface EatNRun{
    eat(food:string):void
    run(distance:number):void
}

interface  Sleep{
    sleep(hours:number):void
}

class Person implements EatNRun,Sleep{ // 多个接口的实现
    eat(food:string):void{
        console.log(food)
    }
    run(distance:number):void{
        console.log(distance)
    }
    sleep(hours:number):void{

    }
}

class Animal implements EatNRun{
    eat(food:string):void{
        console.log(food)
    }
    // 没有run成员就会报错
}

抽象类

抽象类和接口类似,用来约束子类当中必须要有某类成员,抽象类包含具体的实现,而接口只是成员的抽象,不包括具体的实现。大的类目最好使用抽象类。在抽象类中可以定义抽象方法,抽象方法不需要方法体。

abstract class Animal{
    eat(food:string):void{
        console.log(food)
    }
    abstract run(distance:number):void
}

// 非抽象类“Dog”不会实现继承自“Animal”类的抽象成员“run”
class Dog extends Animal{
    run(distance: number): void {
       console.log(distance) // vscode的快速修复功能,可以直接实现
    }

}

泛型

在定义函数、接口、类中没有指定具体的类型,等到使用的时候再去指定的这种特征,其目的是极大程度的复用代码。泛型参数用<T>表示:

// 同样的逻辑只不过是不同的类型,会造成代码冗余
function createNumberArray(length:number,value:number){
    const arr:Array<number> = Array(length).fill(value)
    return arr
}

function createStringArray(length:number,value:string){
    const arr:Array<string> = Array(length).fill(value)
    return arr
}
// 以上代码可以简化为
function createArray<T>(length:number,value:T){
    const arr:Array<T> = Array(length).fill(value)
    return arr
}
// 使用
const res=createArray<string>(3,'a')

类型声明

import {camelCase} from 'lodash' // lodash文件没有声明camelCase的类型

declare function  camelCase(params:string):string

const res=camelCase('hello world')
js ts