大前端学习1-2__解决JavaScript 类型系统的问题以及了解TypeScript

本文主要要了解的是JavaScript自有类型系统的问题以及解决这些问题的方法,而 TypeScript是解决这些问题的方法当中涉及到的语言。

  • TypeScript 解决 JavaScript 类型系统的问题
  • TypeScript 可以大大提高代码的可靠程度

主要了解以下几个重要的点

  1. 强类型与弱类型
  2. 静态类型与动态类型
  3. JavaScript 自有类型系统的问题
  4. Flow 静态类型检查方法
  5. TypeScript 语言规范与基本应用

解决JavaScript 类型系统的问题

从类型安全可以分为强类型 弱类型
从类型检查可以分为静态类型 动态类型
1. 强类型与弱类型

从类型安全的维度编程语言分为 强类型 弱类型

  • 强类型 在语言层面限制函数的实参类型必须与形参类型相同
class Main { 
    static void foo (int num) { 
        System.out.println(num)
    }

    public static void main (String[] args) { 
        Main.foo(100); //  ok 

        Main.foo('100') // error "100" is a string

        Main.foo(Integer.parseInt('100')) // ok
    }
}
  • 弱类型 在语言层面不会限制实参的类型
function foo(num) {
    console.log(num);
}

foo(100) // ok
foo('100') // ok
foo(parseInt('100')) // ok

由于这种强弱之分根本不是某一个权威机构的定义, 当时也没有明确的约定导致对界定细节有不一样的理解

  • 强类型有更强的类型约束, 而弱类型中几乎没有什么约束

  • 强类型中不允许任意的数据隐式类型转换,而弱类型中允许

  • 强类型是语言的语法层面就约束了类型, 如果传入了不同类型爱编译阶段就会报错,而不是在运行中通过判断去限制

变量类型允许随时改变的特点 不是强弱类型的差异(python 的变量是允许改变类型的,但是python是一门强类型语言)

2. 静态类型与静态类型

从类型检查或者类型系统的维度分为静态类型与静态类型

  • 静态类型语言: 一个变量声明时就确定了类型 ,声明后不允许再修改
  • 动态类型语言: 运行时才能明确变量的类型,且变量的类型随时可以改变
var foo = 100
foo = 'bar'
console.log(foo);

动态类型的语言中 变量是没有类型的,变量中存放的值是有类型的

  • 强类型 弱类型(类型安全) 主要区别就是是否允许隐式的类型转换

  • 静态类型 动态类型(类型检查) 主要区别就是类型是否可以随意改变

  • 两个维度的区分不能混淆, 强类型不一定是动态类型 弱类型也不一定是静态类型

JavaScript是一门弱类型且是动态类型的语言 语言本身的类型系统是很薄弱的,甚至可以说是没有类型系统,没有类型限制 灵活多变但是丢失了 类型系统的可靠性。


为什么JavaScript不能是强类型 / 静态类型的语言呢 ?

  1. 之前的JavaScript应用非常的简单 ,在简单需求的时候 类型限制显得多余
  2. JavaScript是一种脚本语言 脚本语言没有编译环节,静态类型语言需要再编辑阶段做类型检查,所以JavaScript也不能设计成静态类型语言

  • 大规模应用下, 没有类型限制这种优势 就编程了短板
3. 弱类型语言开发的问题与强类型的优势
  • 弱类型语言开发的问题
const obj = {} 
obj.foo() 

运行阶段才能发现代码异常 ,不立即运行的话可能一直有隐患,强类型 书写时语法就会报错

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

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

虽然可以通过约定来规避 但是也是治标不治本,多人协作开发的时候约定可能也不会特别清晰,就会导致代码逻辑错误,强类型语言可以完全解决这个问题

const obj = {}

obj[true] = 10 
console.log(obj['true']); // 10

声明的一个布尔值属性名 但是字符串也可以取到 就是因为javaScript是弱类型语言

语法层面的强制要求比约定来的更靠谱些

  • 强类型的优势:
    1. 错误更早暴露 编译就会暴露出问题, 不用等到运行才去debug
    2. 代码更加智能 编码更准确 强类型时代码提示会不起作用
    3. 重构更牢靠 重构指的是对代码有破坏性的改动 例如修改已经存在的成员名称 或者删除对象中的某个成员
    4. 减少不必要的类型判断
4. Flow

Flow是一款JavaScript的类型检查器 为JavaScript提供更完善的类型系统

  • 4.1Flow介绍
// 使用方法:
// @flow 
function square (n: number) { 
    return n * n
}

square('100')

Flow 允许变量后面以冒号的形式写类型注解,根据类型注解判断代码是否存在类 型使用异常

实现开发中规避一些代码类型使用上的错误,就不用了等到运行才能知晓了

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

// a:number 这样的书写方式叫做类型注解

Flow的特点 并不要求给每一个变量都增加类型注解

Flow相比于TypeScript来说只是一个工具几乎不需要什么学习成本,而TypeScript是一门语言需要学习成本比较高

  • 4.2 Flow的使用

    安装flow-bin npm install flow-bin -g --dev
    可以直接去执行flow 作用就是检测当前代码中类型异常

  1. 安装flow检查工具
  2. 添加 @flow注释标记
  3. 添加类型注解
  4. 使用flow命令检测代码
  • 4.3 Flow类型注解移除

    添加了类型注解之后直接运行会报错, 类型注解只在开发时有必要,但是在运行时候没有必要,所以我们要进行移除.

有两种主流方案

  1. 使用low-remove-types

    • 运行 npm install flow-remove-types --dev
      npm install flow-remove-types 第一个参数就是目录 - d 第二个参数为输出目录

    • 一般我们会创建一个src文件来存放源代码, 这样也可以避免我们使用flow去掉了第三方模块的类型注解

    • 实际就是把编写的代码和生产运行的代码分开 ,在这中间加入编译环节 在开发阶段就可以使用flow的方法来区分类型

  2. 使用babel配合插件也可以达到去掉类型注解:
    编译 最常见的编译工具就是babel,babel配合插件也可以达到去掉类型注解的功能

    • 安装babel npm install @babel/core @babel/cli @babel/preset-flow --dev
      @babel/core 是babel的核心模块 @babel/cli可以让我们在命令行直接使用babel命令 @babel/preset-flow就是用来删除类型注解的

    • 需要先自行添加一个babel的文件.babelrc, 在.babelrc 的文件中写入下面代码

    • 之后就可以通过babel命令来去掉类型注解了 npm babel src -d dist 就是把src文件中的源码去掉注解后放到dist目录中

// .babelrc 文件中定义presets
{
    "presets": ["@babel/preset-flow"]
}
  • 4.4 Flow开发工具插件

    Flow这种方法并不那么直观,报错会在控制台进行打印, 如果没有打开控制台那就看不到类型问题的输出,更理想的方式是在代码中直接进行提示

插件扩展中有Flow Language Support 这个是Flow官方提供的插件, 安装后在类型错误的位置会有波浪线去表示

  • 4.5 Flow类型推断 Type Inference

    flow可以根据代码使用情况推断出传入参数的类型, 同时提醒我们就是类型推断,即使在没有使用类型注解的情况下, Flow根据类型推断也可以帮我们判断类型使用的问题,但是还是推荐使用类型注解 。

  • 4.6 Flow类型注解 Type Annotations

    建议尽可能使用类型注解

    标记变量类型以及函数返回值类型 和函数参数类型

// 函数参数类型注解 
function square(n: number) {
    return n * n
}
// 标记变量类型注解 
let num: number = 100

// 函数返回值类型注解

function foo():number {
    return 100
}

  • 4.7 Flow原始类型 Primitive Types
    Flow支持的类型 以及类型注解的高级用法
@flow
const a: string = 'a';
const b: number = 100 || NaN || Infinity // js中 NaN也属于数字 以及无穷大
const c: boolean = false || true

const d: null = null
const e: void = undefined // undefined需要标记为void  和函数参数如果返回undefined 也要标记为void
const f: symbol = Symbol
  • 4.8 Flow数组类型 Array Type
    Flow除了可以支持原始类型 对有结构的类型也是支持的
@flow
const arr1: Array < number > = []

  • 4.9 Flow对象类型 Object Type
@flow
const obj1: {
    foo: string,
    bar: string
} = {foo:'123',bar :'456'}  // 这样定义就要求这个对象必须要有一个 foo 属性和一个bar属性  值类型必须为字符串

const obj2: {
    foo: String,
    bar: number
} = {
    bar: 100
}  // 这样定义就要求这个对象必须要有一个 foo 属性 值类型为字符串 和一个bar属性  值类型必须为数字

const obj3: {
    [string]: string
} = {} //这样定义表示这个对象可以有无数个键值对,前提是键和值都必须为字符串

obj3.key1 = 'value1'
obj3.key2 = 'value3'
  • 4.10 Flow函数类型 Function Type
    函数也是一个数据类型所以会作为参数 , 函数作为参数时 类型注释写法如下:
@flow
function foo(callback: (string, number) => void) {
    callback('string', 100)
}
foo(function (str, n)) {
    // str => string
    // n =>number 
}
  • 4.11 特殊类型
@flow

// 1. 字面量类型 
const a:'foo' = 'foo' 

// 2.. 联合类型或者 | 或类型
const type: 'success' | 'warning' | 'danger' = 'success'
// 还可以用type声明一个类型用于表示联合类型以后的结果
type StringOrNumber = string| number  相当于别名 可以重复使用
const b : StringOrNumber  = 100 

// 3. maybe类型
const gender: ?number = nudefined
// maybe类型相当于在基础类型的基础上扩展了 null 和undefined 用一个问号
const gender: number | void | null = undefined

  • 4.12 mixed类型

    mixed类型可以接收任何类型的数据,实际上是所有类型的联合类型

// mixed = string | number | null | boolean | Function | Array | Object
function passMixed(value:mixed) {
    if (typeof value === 'string') { 
        value.substr(1)
    }
    if (typeof value === 'nunmber') { 
        value * value
    }
}
passMixed('string')
passMixed(100)

// any类型也有和mixed有一样的效果

function passMixed(value:any) {
    value.substr(1)

    value * value
}
passMixed('string')
passMixed(100)

两者差异 any是弱类型 mixed是强类型

实际使用尽量不要使用any类型 ,any存在的意义是为了兼容旧项目的代码

  • 4.13 Flow运行环境支持的API

就是运行环境所产生的类型限制

const element: HTMLElement | null = document.getElementById(‘app’) // 这个位置如果传入数字 会报错 然后返回一个html的节点,如果没有找到会返回null

TypeScript

  • TypeScript是JavaScript的超集
  • 支持转换新特性,最低可以编译ES3版本
  • 任何一种JavaScript运行环境都支持 因为最后TypeScript是编译成JavaScript的
  • 相比Flow 生态健全 更完善 功能更强大 Flow稍微有点卡
  • TypeScript是前端领域中的第二语言

缺点是:

  1. 语言本身多了很多概念 接口 泛型 枚举(TypeScript属于渐进式的 即使没有了解过也可以当做JavaScript去使用)

  2. 项目初期 TypeScript 会增加一些成本

1. TypeScript的使用

先安装 npm i --save --dev typescript

const hello = name => { 
    console.log(`hello , ${name}`);
}

hello('zhangsan')

yarn tsc 04-TypeScript.ts 运行之后会产生一个同名的js文件 

const hello =( name:string) => { 
    console.log(`hello , ${name}`);
}

hello('zhangsan')

  1. 安装模块 安装后TypeScript提供了一个编译的模块 tsc
  2. tsc命令就可以去编译TypeScript文件 先检查类型使用异常 然后移除类型注解 还会转换ECMAScript的新特性
2. 配置文件
运行命令
npm i typescript --dev
tsc .\04-TypeScript.ts

生成配置文件 tsconfig.json 运行命令 tsc --init
文件内容如下

{
    "compilerOptions": {
        "module": "commonjs", // 输出代码用什么模式进行模块化
        "target": "es5", // 设置编译后的js所采用的的ECMAScript的标准
        "noImplicitAny": false,
        "sourceMap": false, // true 开启源代码映射,可以通过.map 的文件去调试TypeScript文件的代码 
        "outDir": "dist" ,// 代码编辑结果输出的文件夹 
        "strict": true // 开启所有严格检查的选项 类型检查不会十分严格 严格模式下需要给每个变量声明类型
    },
    "exclude": [
        "node_modules"
    ]
}

3. 原始类型
const a: string = 'foo'
const b: number = 123
const c: boolean = true
const d:string = null // "strict": true 提示为不能将类型“null”分配给类型“string”。


const e:void = undefined | null // 严格模式下 只能是undefined


const f :null = null 
const g: undefined = undefined

const h: symbol = Symbol()  直接创建会报错  "Symbol" 仅指类型,但在此处用作值。因为在配置文件中的 target选择的是es5 而Symbol是es2015中声明的 所以会报错
4. 标准库声明

标准库-- 就是内置对象所对应的声明文件

解决symbol报错有两种方法

  1. 修改target的值为es2015
  2. 配置文件中的lib是一个数组 可以写多个标准库

lib中的数据会覆盖 target中的, 所以lib的标准库中尽量写全当前项目应用的所有的标准库

5. 显示中文错误消息
  1. 可以运行 tsc --locale zh-CN // 即可显示报错为中文消息
  2. 在vsCode中设置 文件 - 首选项 - 设置 - 搜索TypeScript-locale 设置值为zh-CN
6 . 作用域问题

不同文件中变量名称相同
声明在全局的变量名称重复会报错

  1. 解决办法是用函数包起来

  2. 可以把文件以模块的形式导出, 模块是有自己的作用域的 所以也是可以的
    export { }

7. Object类型

Object类型 并不仅仅指对象它泛指除了原始类型以外的其他类型包括 函数 数组 和对象这几种类型

export { }

const foo: object = function () {

}

// 如果要确保声明的为对象 可以使用对象字面量的形式 
const obj: { foo: string, bar: number } = {
    foo: 'value',
    bar:123
}

更专业的方式是使用接口

8. 数组类型
const arr1: Array<number> = [1, 2]
const arr2: number[] = [1, 2, 3]

function sum(...args :number[]) {
需要判断每个数组成员都是数字 之前需要一堆判断 现在只需要给参数加number【】即可
    return args.reduce((prev, current) => prev + current, 0 )
}

9. 元祖类型 Tuple Types
const tuple: [number, string] = [18, 'zhangsan']

const age = tuple[0]
const name = tuple[1]

const [age,name] = tuple
元祖一般用来在函数中返回多个返回值

Object.entries() // 这个方法就返回一个元祖 类型和长度是固定的 
10. 枚举类型 Enum Types

经常会用到用某几个值表示不同的状态
一般js中会使用一个对象来模拟枚举类型

const postStatus = {
    Draft: 0,
    Unpublished:1,
    published:2
}

const post = {
    title: 'hello world',
    content: 'bye bye world',
    // status: 0
    status: postStatus.Draft

}

枚举类型的特点 :
1. 可以给每个数据起更好的名字来说明这个值
2. 枚举中只会存在固定的值

TypeScript中会使用Enum来声明一个枚举类型

enum PostStatus { 
    Draft = 0,
    Unpublished = 1,
    published = 2
}

注意: 枚举值位数字的话,枚举的值可以不指定, 不指定的话默认从有值的枚举值开始累加

枚举的值除了可以是数字之外还可以是字符串, 字符串需要写入每个枚举值

枚举类型会入侵运行代码也可以说影响编译结果,枚举最后会编译称为一个双向的键值对对象

双向的键值对对象: 就是可以通过值去获取键 也可以通过键去获取值

如果不需要去获取枚举的键和值建议使用常量枚举
就是在enum前加const 这样的话编译过后 在枚举值的地方都会替换成对应的值

11 函数类型

函数声明式

function func1(a:number, b:number) :string {
    return 'func1'
}

func1(10,20)

函数表达式

const func2:(a:number,b:number) => string = function (a:number,b:number):string {
    return 'func2'
} // 函数表达式是一个变量,变量有可能会成为参数。所以写法为上面类型箭头函数的写法 规定变量func2参数以及返回值的类型

12 任意类型

由于js为弱类型 很多内置API会支持任意类型的数据,ts基于js所以需要一个类型来接受任意类型的数据

function stringify(value:any) {
    return JSON.stringify(value)
}

stringify('string')
stringify(123)
stringify(true)
let foo: any = 'string'

foo = 123

foo.bar()

any类型在语法上的书写就可以像js一样 语法上都不会报错

any类型不会存在类型检查 但是不建议去用any类型

13 隐式类型推断

没有明确用类型注解去标记这个变量

let age = 18 // 此时ts会推测这个变量类型为number

age = ‘string’ // 推测为数字类型后再赋值其他类型会报错

如果无法推断类型 就会默认这个变量的类型是any,如果赋值为数字就会推断为数字类型

14 类型断言

一些时候ts是无法推断变量类型的,但是作为我们开发人员是知晓的
假定这个nums 来自一个明确的接口

const nums = [11, 12, 13, 14]

const res = nums.find(i => i > 0)

const square = res * res 

断言的两种写法

1. const num1 = res as number 
2. const num2  = <number>res  // JSX下不能使用

类型转换不是类型断言, 类型断言是发生在编译过程的 运行时会去掉,而类型转换是发生在运行过程中的

15 接口 interfaces

一种规范一种契约 约定对象的结构 一种抽象的概念

明确的体现就是去约定一个对象的成员和成员的类型

function printPost(post) {
 这样是隐式的要求我们传入的post必须要有title和content两个属性
console.log(post.title);
console.log(post.content);
}

明确的写法:

interface Post {
    title: string,
    content: string
}

function printPost(post : Post) {
    console.log(post.title);
    console.log(post.content);
}

printPost({
    title: '123',
    content:'456'
})


printPost({
    title: 123, //不能将类型“number”分配给类型“string”
    content:'456'
})

接口只是做开发时的类型约束的,实际运行中接口并没有意义

接口中的约束的成员 有一些特殊的
可选成员 只读成员 动态成员

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

// 动态成员
interface Cache { 
    [prop:string] : string //动态成员
}

const cache: Cache = {}

cache.foo = 'value2'
cache.bar = 'value2'
16 类 classes

面向对象编程中一个最重要的概念

作用 描述一类具体事物的抽象特征 手机属于一个类型 智能手机

TypeScript中增强了class的相关语法 特殊访问修饰符 和

新的类的特性

class Person {
    // 参数需要先声明 以此来给参数做类型标注 否则会报错
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name},${msg}`);

    }
}
17 访问修饰符
class Person {
    // 参数需要先声明 以此来给参数做类型标注 否则会报错
    public name: string
    private age: number
    protected gender: boolean
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name},${msg}`);

    }
}

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 tom = new Person('tom', 18)
console.log(tom.name);
console.log(tom.age); //私有
console.log(tom.gender); // 子集可以访问
  1. public // 公有的 所有的成员默认都是公有的可以不加
  2. private // 私有的 外部不可以访问
  3. protected // 只允许在子类中访问的成员

构造函数的访问修饰符 如果设置有私有的话就只能在类的内部来使用 不能实例化和在外部使用了 ,
可以在类的内部使用 static 来创建一个静态方法来实例化这个类以便于在外面调用

设置类为只读 readonly如果属性已经有访问修饰符了 只读应该写在访问修饰符的后面

18类与接口

类与类之间重合的可以用接口抽象出来

i

nterface EatAndRun {
    eat(food: string): void
    run(distance: number): void
}

interface Eat {
    eat(food: string): void
}
interface Run {

    run(distance: number): void
}

implements Eat, Run || implements EatAndRun
class Person implements EatAndRun{
    eat(food: string) {
        console.log(`优雅吃饭: ${food}`);

    }

    run(distance: number) {
        console.log(`直立行走:${distance}`);

    }
}

class Animal implements Eat, Run {
    eat(food: string) {
        console.log(`呼噜噜: ${food}`);

    }

    run(distance: number) {
        console.log(`爬行:${distance}`);

    }
}
19 抽象类

用abstract来定义 不能用new来创建

abstract class Animal {
    eat(food: string) {
        console.log(`呼噜噜: ${food}`);

    }

    abstract run(distance: number): void
}

class Dog extends Animal {
    run(distance: number): void {
        console.log(`四脚爬行: ${distance}`);

    }
}

const d = new Dog()
d.eat('enenen')
d.run(100)
20 泛型 Generics

指的是定义的时候没有明确类型 ,运行的时候才明确具体类型

function createNumberArray(length: number, value: number): number[] {
    const arr = Array<number>(length).fill(value)
    return arr
}

function createStringArray(length: number, value: string): string[] {
    const arr = Array<string>(length).fill(value)
    return arr
}

function createArray<T>(length: number, value: T): T[] {
    const arr = Array<T>(length).fill(value)
    return arr
}

const res = createNumberArray(3,100)

const res = createArray<string>(3,'123')

21 类型声明

作用就是为了兼容普通的模块

描述为应用的某个变量或者方法在定义的时候并没有明确类型 ,所以可以用declare 来重新强调声明一下。

以引入模块为例:

import { camelCase } from 'lodash' //.d.ts文件就是TypeScript的类型声明的文件 

declare function camelCase(input: string): string 

const res = camelCase('hello world')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值