【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)

【Part1作业】https://gitee.com/zgp-qz/part01-task

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)

【Part2作业】https://gitee.com/zgp-qz/part02-homework

扫描二维码关注公众号,回复: 12895358 查看本文章

课程概述

TypeScript 是一门基于 javascript 之上的编程语言,它重点解决了 javascript 语言自有的类型系统的不足。通过适用 TypeScript 语言可以大大提高代码的可靠程度。

虽然这里说的是 TypeScript ,其实这里要介绍的内容重点是探讨 javascript 自有类型系统的问题以及如何去借助一些优秀的技术方案去解决这些问题。

而 TypeScript 只是这个过程当中涉及到的语言。

因为 TypeScript 目前可以说是此类问题的最终极解决方案,所以会着重去学习它。除此之外,也会去介绍一些其他的相关技术方案。

大致分为以下几个阶段:

  1. 到底什么是强类型,什么是弱类型

  2. 什么是静态类型,什么是动态类型
    他们之间有什么不一样,以及为什么 javascript 是弱类型的,为什么是 动态类型的

  3. 了解 javascript 自有类型系统存在的问题,以及这些问题对我们的开发工作都造成了哪些影响

  4. Flow 静态类型检查方案
    只是一个小工具,弥补了 javascript 类型系统的不足

  5. TypeScript 语言规范与基本适用
    是基于 javascript 之上的一门编程语言,不过它属于渐进式的,即便什么特性都不知道,也可以按照 javascript 的语法去使用它。所以在学习上来讲的话就可以学一点,用一点。

强类型与弱类型

区分不同编程语言时经常提及的名词:

  1. 强类型和弱类型:从 类型安全 维度区分
  2. 静态类型和动态类型:从 类型检查 维度区分

类型安全:强类型 VS 弱类型

强类型:在语言层面就限制了函数的实参类型必须与形参类型相同。
弱类型:在语言层面并不会限制实参的类型。

由于强弱类型之分根本不是某一个权威机构的定义,而且当时的两位计算级专家也没有给出一个具体的规则,所以后面导致后人对这种界定方式的细节出现了一些不一样的理解。

但是整体上大家的界定方式都是在描述:强类型有更强的类型约束,而弱类型语言几乎没有什么类型约束。

个人比较同意的说法:强类型语言当中不允许有任意的隐式类型转换,而在弱类型语言当中则允许任意的隐式类型数据转换。

总结:强类型就是不允许有随意的隐式类型转换,而弱类型允许。

注意:变量类型允许随时改变这样的特点不是强弱类型之间的区别。

静态类型与动态类型

除了类型安全的角度分为强类型和弱类型之分,在类型检查的角度还可以将编程语言分为静态类型与动态类型。

静态类型的表现就是:一个变量声明时,它的类型就是明确的,而且在这个变量声明过后它的类型就不允许再被修改。

动态类型的表现是:在运行阶段才能够明确的变量类型,而且变量的类型也可以随时发生变化。

也可以这么说:在动态类型语言中,变量是没有类型的,而变量中所存放的值是有类型的。

javascript 就是一门标准的动态类型语言。
在这里插入图片描述

javascript 类型系统特征

由于 javascript 是一门弱类型且是动态类型的语言,语言本身的类型系统是非常薄弱的,甚至可以说 javascript 根本就没有一个类型系统,这种语言的特征,用一个比较流行的词来说就是:‘任性’;因为它几乎没有任何的类型限制,所以说 javascript 这门语言也是极其灵活多变的。但是在这种灵活多变的表象背后,丢失掉的就是类型系统的可靠性。

在代码中每每遇到一个变量都需要担心它到底是不是我们想要的类型;那整体的感受,用另外一个流行的词来说就是:‘不靠谱’;

那为什么 javascript 不能设计成一门强类型 / 静态类型 更靠谱的语言呢?

这个原因自然跟 javascript 的背景有关。

早前,javascript 根本就没有想到会发展成今天这种规模(应用简单),最早的 javascript 应用根本不会有这么复杂,需求都非常简单,很多时候几百行代码,甚至几十行代码就搞定了,那在这种一眼就能够看到头的情况下,类型系统的限制就会显得很多余。

且 javascript 是一门脚本语言,脚本语言的特点就是不需要编译就直接在运行环境中去运行,换句话说,javascript 没有编译环节,即便把它设计成一门静态类型语言,也没有什么意义。因为静态类型语言需要在编译阶段去做类型检查,而 javascript 根本就没有这样一个环节。

根据以上的这些原因,javascript 就成为了一门更灵活,更多变的 弱类型 / 动态类型 语言。

放在当时的环境当中,这些并不是 javascript 的弱点,甚至可以说是 javascript 的一大优势,而现如今,前端应用的规模已经完全不同,遍地都是一些大规模的应用。那我们 javascript 的代码也会变得越来越复杂,开发周期也会变得越来越长,在这种情况下,javascript 弱类型 / 动态类型 的这些优势,也就自然变成了它的短板。

举个例子:
以前杀鸡,用小刀子就可以了,但是现在用这把小刀子去杀牛,就会变得非常吃力。

弱类型的问题
  1. 因为弱类型的关系,代码中存在一些运行时的异常,需要等到运行的时候才能发现
  2. 因为弱类型的原因,因为类型不明确,会造成函数的功能可能会发生改变。
  3. 因为弱类型的关系,出现了对对象索引器的错误的用法

综上:

在代码量小的情况下,我们可以通过约定的方式去规避,而对于一些开发周期特别长的大规模项目,这种君子约定的方式仍然会存在隐患,只有在语法层面的强制要求,才能够提供更可靠的保障。

// JavaScript 弱类型产生的问题

// 1. 异常需要等到运行时才能发现

// const obj = {}

// // obj.foo()

// setTimeout(() => {
    
    
//   obj.foo()
// }, 1000000)

// =========================================

// 2. 函数功能可能发生改变

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

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

// =========================================

// 3. 对象索引器的错误用法

const obj = {
    
    }

obj[true] = 100 // 属性名会自动转换为字符串

console.log(obj['true'])

强类型的优势
  1. 错误可以更早的暴露:在编码阶段提前去消灭一大部分有可能会存在的类型异常。
  2. 代码更智能,编码更准确:例如开发工具的智能提示
  3. 重构会更加牢靠:例如修改一个变量名称,如果是强类型的话后面的所有用到这个名称的地方都会报错,甚至有的开发工具会帮助你把所有使用到这个变量的地方自动的修改过来
  4. 减少代码层面不必要的类型判断
// 强类型的优势

// 1. 强类型代码错误更早暴露

// 2. 强类型代码更智能,编码更准确

// function render (element) {
    
    
//   element.className = 'container'
//   element.innerHtml = 'hello world'
// }

// =================================

// 3. 重构更可靠

// const util = {
    
    
//   aaa: () => {
    
    
//     console.log('util func')
//   }
// }

// =================================

// 4. 减少了代码层面的不必要的类型判断

function sum (a, b) {
    
    
  if (typeof a !== 'number' || typeof b !== 'number') {
    
    
    throw new TypeError('arguments must be a number')
  }

  return a + b
}

Flow 概述

Flow 是一个 javascript 的静态类型检查器,2014年 facebook 推出的一款工具。使用它可以弥补 javascript 弱类型带来的一些弊端,也可以说它为我们的 javascript 提供了一个更为完善的类型系统。

目前,在 react 和 vuejs 项目中都可以看到 flow 的使用,足以见得,flow 是一个非常成熟的技术方案。

它的工作原理就是在我们的代码当中,通过添加一些类型注解的方式,来标记变量或者是参数它应该是什么类型的。然后 flow 根绝这些类型注解,就会去检查代码当中是否会存在一些类型使用上的异常。从而去实现,在开发阶段对类型异常的一个检查。这就避免了在运行阶段才去发现这种类型使用上的一些错误。

对于代码中的这些类型注解,可以在代码运行之前通过 babel 或者 flow 官方提供的一个模块,自动去除。所以在生产环境当中,这些类型注解不会有任何的影响。

flow 还有一个特点就是并不要求给每一个变量都去添加类型注解,这样的话完全可以根据自己的需要去添加,需要一个加一个。

相比于后面要介绍的 TypeScript ,flow 只是一个小工具,而 TypeScript 是一门全新的语言。所以 Flow 几乎没有什么学习成本,使用起来特别简单。

Flow 快速上手

因为 Flow 是以一个 npm 模块的形式工作的,所以需要安装。

npm init --yes  # 初始化 package.json
npm install flow-bin --save-dev  # 安装 flow 的类型检查工具模块

新建一个文件:01-getting-started.js 文件,在最上边通过注释的方式添加 @flow 说明这里需要 flow 来做类型检查

// @flow 
/**这里的 @ flow 做标记,标记这里需要 flow 做检查 */
function sum(a: number, b: number) {
    
    
    return a + b;
}

sum(100, 200)
sum('100', '200')

添加注解之后 vsCode 会报语法错误,因为注解(:number)不是标准的 javascript 语法,而 vsCode 会自动校验 javascript 语法,所以会报错。因为这里使用了 flow ,所以自然也就不需要 vsCode 自带的语法校验了。

关闭 vsCode 语法校验:
首选项 – 设置 – 搜索 javascript validate,关闭语法校验
在这里插入图片描述
接下来就可以使用 flow 去检查代码中存在的问题了。

npm run flow init  # 初始化一个 flow 的配置文件

在 package.json 的 scripts 里边添加一个指令

{
    
    
	"scripts":{
    
    
		"flow":"flow"
	}
}

在终端命令行运行:npm run flow

就能看到报了 flow 帮我们找到了代码中的 2个 错误:
在这里插入图片描述
从这里就可以看出来 flow 类型检查的意义了。

这里首次启动 flow 的时候 flow 会自动在后台启动一个服务,所以首次相对会慢一点,再次运行 flow 检查的时候就会快了,同样的如果这里不需要做什么检查了,可以手动关闭 flow 启动的这个服务,运行 npm run flow stop 关闭掉就行了。

Flow 编译移除注解

因为类型注解这种方式不是 js 里面标准语法,所以当前代码是没办法直接运行的。直接运行会报语法错误。

想移除也非常简单,这里可以使用工具在代码完成之后可以自动移除类型的注解。

官方推荐的是 flow-remove-types 这样一个模块,这也是最快速,最简单的一个方法。

npm install flow-remove-types --save-dev  # 安装 flow-remove-types 模块

安装成功之后就可以使用命令行工具执行这个工具命令,自动移除类型注解了。
首先在 package.json 里面的 scripts 添加一个指令:flow-remove-types . -d dist 这里的第一个参数 .(点) 表示当前目录,-d 表示输出到指定目录 dist 是输出到的目录

"scripts": {
    
    
    "flow": "flow",
    "rm-types": "flow-remove-types . -d dist"
},
npm run rm-types # 直接运行这个就行

在看当前目录下边多了一个 dist 文件夹,下方的 js 文件就是我们去除类型注解之后的可直接运行的 js 文件。
在这里插入图片描述
类似于这种操作,一般我们会在项目的根目录下新建一个 src 的文件夹,把我们所有的源代码放入到 src 目录内,编译的结果呢,输出到 dist 目录内,这样就可以避免在转换的过程当中误删掉一些第三方模块里面的类型注解。

package.json 里面的 scripts 指令改一下当前目录:
在这里插入图片描述
再次运行 npm run rm-types ,当前目录下的 dist 下面的文件,就是去除类型注解之后的文件了。

通过这些操作就能够看出来 flow 这种方案是如何去解决 javascript 中,弱类型所带来的弊端的。无外乎就是把我们编写的代码和实际生产环境运行的代码分开,然后再中间加入了编译的环节。这样的话就可以在开发阶段使用一些扩展语法使得我们的类型检测变成可能。

讲到编译,那么 javascript 里边最常见的编译工具就是 babel ,bebel 去配合一个插件也可以去实现自动去移除代码当中的类型注解。这里尝试一下:

@babel/core -- babel 的核心模块
@babel/cli -- babel 的 cli 工具,使可以在命令行运行 babel
@babel/preset-flow -- 包含了 flow 转换类型注解的插件

npm install --save-dev @babel/core @babel/cli @babel/preset-flow

安装完成过后这里就可以使用 babel 命令去自动编译我们这里的 js 代码了,在编译过程当中,它可以帮我们自动移除代码中的类型注解。

先在项目中添加一个 babel 的配置文件:.babelrc 文件,在文件当中添加一个 presets 的配置:

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

在这里插入图片描述
这时候就可以运行 babel 命令去编译文件了
在 package.json 里边的 scripts 添加一个 babel 编译的命令:

"scripts": {
    
    
    "flow": "flow",
    "rm-types": "flow-remove-types src -d dist",
    "babel-rm-types": "babel src -d dist"
},

命令行直接运行 npm run babel-rm-types 就行了
在这里插入图片描述
在这里插入图片描述
可以看到,编译过后的 js 文件类型注解都被移除掉了。

Flow 开发工具插件

上面所讲的这种方式都是运行 flow 命令,然后将代码当中存在的类型错误问题输出到了命令行当中,而我们开发过程当中需要开命令行终端去运行命令才能看到对应的类型问题。这种体验非常不好,并不是很直观。

更好的方式当然是在开发过程当中开发工具直接去显示出来当前代码存在的类型错误的一些问题。所以这里对于 flow 一般会选择安装一个 开发工具的插件 ,让开发工具可以更直观的体现代码中存在的问题。

这里的开发工具是 vsCode 。

直接在插件处搜索 flow :
在这里插入图片描述
Flow Language Support ,是 flow 官方所提供的一个插件,安装过后在 vsCode 的工作状态的状态栏就可以显示 flow 插件的工作状态。而且在代码中,类型的异常直接被标记出来了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里需要注意的问题是:

代码需要保存过后 flow 才能去检查代码中存在的问题,所以在编写过程当中可能会感觉有一些迟钝。原因就是 flow 这个插件并不是 vsCode 原生自带的功能,所以相对来讲没有那么好的体验。

如果是使用的其他编辑器,其支持的插件也可以去 flow 官网去查看对编辑器支持的情况:https://flow.org/en/docs/editors
在这里插入图片描述

Flow 类型推断

在我们的代码当中,除了可以使用类型注解的方式去标记我们所使用的类型,flow 还可以很聪明的自动帮我们推断我们代码当中每一个成员的类型。

/**
 * 类型推断
 * @flow
 */

 // 这里正常需要给 n 添加一个类型注解 n: number,但是我们没去添加这个类型注解
function square(n) {
    
    
    return n * n
}

square('100')

在这里插入图片描述
这时候 flow 仍然可以发现在使用类型上的错误,flow 可以根据我们传入的参数的类型去判断我们类型使用上的错误。

这种根据我们代码中变量的使用情况,去推断出我们所使用的类型这样的一个特征就叫做:类型推断。

Flow 类型注解

在绝大多数情况下,flow 都可以像上面所说的那样帮我们推断出来变量或者是参数的具体类型,所以从这个角度来讲,没必要给所有的成员都去添加类型注解。但是去添加类型注解可以更明确的去限制类型,而且对后期去理解这里的代码,是更有帮助的。建议还是尽可能去使用类型注解。

类型注解不仅仅可以使用在参数上,还可以标记变量的类型以及函数返回值的类型。

/**
 * 类型注解
 *
 * @flow
 */

function square (n: number) {
    
    
  return n * n
}

let num: number = 100

// num = 'string' // error

function foo (): number {
    
    
  return 100 // ok
  // return 'string' // error
}

function bar (): void {
    
     // 没有返回值的函数类型标记为 void
  // return undefined
}

Flow 原始类型

javascript 当中的原始类型
number: 数字,NaN,Infinity(无穷大)
undefined: flow 中的 undefined 是用 void 来表示的,undefined 对应的类型为 void

/**
 * 原始类型
 *
 * @flow
 */

const a: string = 'foobar'

const b: number = Infinity // NaN // 100

const c: boolean = false // true

const d: null = null

const e: void = undefined

const f: symbol = Symbol()
Flow 数组类型
/**
 * 数组类型
 *
 * @flow
 */

const arr1: Array<number> = [1, 2, 3]

const arr2: number[] = [1, 2, 3]

// 元组
const foo: [string, number] = ['foo', 100]

Flow 对象类型
/**
 * 对象类型
 *
 * @flow
 */

const obj1: {
    
     foo: string, bar: number } = {
    
     foo: 'string', bar: 100 }

const obj2: {
    
     foo?: string, bar: number } = {
    
     bar: 100 }

const obj3: {
    
     [string]: string } = {
    
    }

obj3.key1 = 'value1'
obj3.key2 = 'value2'
Flow 函数类型
/**
 * 函数类型
 *
 * @flow
 */

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

foo(function (str, n) {
    
    
  // str => string
  // n => number
})
Flow 特殊类型
/**
 * 特殊类型
 *
 * @flow
 */

// 字面量类型

const a: 'foo' = 'foo'

const type: 'success' | 'warning' | 'danger' = 'success'

// ------------------------

// 声明类型

type StringOrNumber = string | number

const b: StringOrNumber = 'string' // 100

// ------------------------

// Maybe 类型

const gender: ?number = undefined
// 相当于
// const gender: number | null | void = undefined

Flow Mixed 与 Any

Any 是弱类型,Mixed 是强类型.

因为 Any 类型它不会去检查类型是否使用正确,Mixed 还是会检查类型的使用是否正确,如果使用不正确还是会报错,需加上类型判断之后才会不报错。

实际使用过程当中尽量不要使用 Any 类型,Any 类型存在的意义实际上是去兼容以前老的代码中的一些情况。

/**
 * Mixed Any
 *
 * @flow
 */

// string | number | boolean | ....
function passMixed (value: mixed) {
    
    
  if (typeof value === 'string') {
    
    
    value.substr(1)
  }

  if (typeof value === 'number') {
    
    
    value * value
  }
}

passMixed('string')

passMixed(100)

// ---------------------------------

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

  value * value
}

passAny('string')

passAny(100)
Flow 类型小结

Flow 当中的类型还有很多,这里学习 Flow 最主要的目的是以后去理解一些第三方项目(vue,react)的源码时,可能会遇到项目中使用的 flow 的情况,所以说我们必须要能够看懂。

不过在这些项目当中可能会存在一些我们没了解过的类型,这些类型呢,到时候可以查一些相应的文档。
https://flow.org/en/docs/types/
在这里插入图片描述
第三方的类型手册:https://kaiwu.lagou.com/xunlianying/index.html?courseId=17#/detail?weekId=765&lessonId=1547
在这里插入图片描述

Flow 运行环境 API

因为 javascript 不是独立工作的,它必须要运行在一个独立的特定的运行环境当中,例如:浏览器环境,node 环境等,这些运行环境一定会提供一些 API 给我们使用,例如浏览器环境的 BOM 和 DOM,node 环境中的各种模块,也就是说我们的代码必然会使用到这些环境中所提供的一些 API或者说对象,对于这些 API 或 对象,同样会有一些类型的限制。

/**
 * 运行环境 API
 *
 * @flow
 */

const element: HTMLElement | null = document.getElementById('app')

https://github.com/facebook/flow/tree/master/lib/core.js
https://github.com/facebook/flow/tree/master/lib/dom.js
https://github.com/facebook/flow/tree/master/lib/bom.js
https://github.com/facebook/flow/tree/master/lib/cssom.js
https://github.com/facebook/flow/tree/master/lib/node.js
在这里插入图片描述

TypeScript 概述

TypeScript 是 JavaScript 的超集(superset)或者扩展集,就是在 javascript 基础之上多出来一些扩展特性,多出来的实际上就是一个更强大的类型系统以及对 ECMAScript 新特性的支持。
在这里插入图片描述
它最终还是会被编译为原始的 javascript。
在这里插入图片描述
也就是说我们使用 TypeScript 过后,在开发过程中就可以直接去使用 TypeScript 所提供的这些新特性,以及 TypeScript 当中更强大的类型系统,去完成我们的开发工作。

完成开发工作之后,再将代码编译成能在生产环境直接去运行的 JavaScript 代码。

TypeScript 类型系统的优势,同 Flow 一样,帮我们避免在开发过程当中有可能会出现的类型异常,去提高我们编码的效率,以及我们代码的可靠程度。

对于新特性的支持,在很多环境当中都会有一些兼容性的问题,而 TypeScript 支持自动去转换这些新特性,所以说我们就可以在 TypeScript 当中去立即使用这些新特性,那也就是说即便我们不需要类型系统(不想要这种强类型系统),通过 TypeScript 去使用 ECMAScript 的一些新特性,也是一个很好的选择。

因为 TypeScript 最终是编译成 javascript 去工作,所以说,任何一种 JavaScript 运行环境下的运行程序,都可以使用 TypeScript 去开发。

相比较于前边的 Flow ,TypeScript 作为一门完整的编程语言,它的功能更为强大,重要的是它的生态更加健全、完。特别是对于开发工具这一块,微软自家的开发工具对 TypeScript 的支持都特别友好。

比如之前在 vsCode 中使用 flow 会感觉有些迟钝,有些卡,但是去使用 TypeScript 的话,会感觉非常的流畅。

目前有很对大型的开源项目都已经使用 TypeScript 去开发了,最知名的就是 Angular/Vue.js3.0。

这时候就会发现,TypeScript 是前端领域中的 第二语言,如果是小项目,需要灵活、自由,那么自然会选择 JavaScript 本身,相反,如果是长周期开发的大型项目,所有人都会建议你选择 TypeScript 。

当然,TypeScript 也会有些缺点,其中最大的缺点就是:TypeScript 语言本身多了很多概念,例如:接口、泛型、枚举等等。

好在它是渐进式的,即便我们什么都不知道,也可以立马按照 JavaScript 的语言格式去编写,也就是说你完全可以把它当作 javascript 去使。然后在学习的过程当中,了解到一个特性,去使用一个特性。

再就是对于周期比较短的小型项目,TypeScript 可能会增加一些开发成本。因为在项目初期需要编写很多的类型声明,比如说对象,函数等都会有很多的类型声明需要单独编写。

如果是一个长期,大型的项目,这些成本根本不算什么,而且很多时候都是一劳永逸的,这样来看,它给带来的优势是远大于这些小问题的。

所以整体上来讲,TypeScript 是前端领域发展中一门必要的语言。

TypeScript 快速上手

这里先快速了解一下 TypeScript 的快速适用。

想要适用 TypeScript ,首先要先安装一下,TypeScript 本身就是一个 npm 的模块,可以选择安装到全局,但是考虑到项目的依赖问题,推荐安装到项目当中,这样更加合理更加方便迁移查看。

npm init --yes
npm install --save-dev typescript

在这里插入图片描述

安装成功之后就可以去编写我们的代码了。

新建 01-getting-started.ts 看好是 ts 结尾

因为 ts 是完全基于 javascript 标准之上的,所以这里也完全可以按照 javascript 标准来编写代码:

// 完全按照 javascript 标准编写代码,
const hello  = name => {
    
    
    console.log(`Hello ${
      
      name} !`)
}

hello('Tom')

上面的代码是按照当前最新的 javascript 标准编写的一段普通的代码,并没有任何特殊的用法,现在尝试用 ts 来编译一下看看:

package.json 里添加 scripts 指令:

"scripts": {
    
    
  "test": "echo \"Error: no test specified\" && exit 1",
  "tsc-compile": "tsc 01-getting-started.ts"
},

运行 npm run tsc-compile

可以看到,同目录输出了一个同名的 01-getting-started.js 文件
在这里插入图片描述
可以看到,这里所使用的所有的 ES6 的部分都被编译成了标准的 ES3 标准的语法。

这也就印证了之前说的,即便不需要 TypeScript 所提供的类型系统,也可以使用 TypeScript 去直接使用最新的 ECMAScript 特性。这也是 TypeScript 的一个主要的功能。

除了编译转换 ES 的一些新特性,它更重要的是为我们提供了一套更强大的类型系统。这里去使用一下,使用方式同之前的 Flow 的方式基本上是完全相同的。

// 完全按照 javascript 标准编写代码,
const hello  = (name:string) => {
    
    
    console.log(`Hello ${
      
      name} !`)
}

hello('Tom') 
hello(100) 

在这里插入图片描述
添加完类型注解之后如果在外面调用传递的参数不是个 string 类型,再次去编译就会报一个类型使用错误,而且从这里也能看到,vsCode 默认就支持 对 typeScript 语法的检查,所以这里就不用等到编译,在编辑器当中就能直接考到类型错误的提示。

尝试编译一下:
在这里插入图片描述
在编译过程也会报错这个类型错误。

再把参数类型修改正确,然后会到命令行再次编译,就会发现编译成功并生成了一个对应的 js 文件。
在这里插入图片描述

TypeScript 配置文件

tsc 这个命令不仅仅是编译指定的文件,还可以用来编译整个工程/项目,不过一般在编译整个项目之前,会先给这个项目创建一个 typescript 的配置文件,这里使用 typescript 的命令自动生成一个配置文件:

.\node_modules\.bin\tsc --init

如果不想添加前边的 .\node_modules\.bin\ 前缀,可以将指令放入 package.json 里边的 scripts 里面,这样可以直接写 tsc 指令而不需要添加前缀

"scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1",
    "tsc-compile": "tsc 01-getting-started.ts",
    "init-ts-config":"tsc --init"
},

命令行直接运行 npm run init-ts-config 也能生成 ts 的配置文件,看自己爱好选择不同的情况就行

项目根目录就会多出一个 tsconfig.json 这样一个文件,里边的内容默认包含的就是 TypeScript 编译器所对应的选项。
在这里插入图片描述
可以看到,绝大多数选项都被注释掉了,大部分选项后边都会配有简要的说明。
简单的说几个:

"target": TS 编译后所对应的 JS 标准,这里默认是 ES5,也就是说 TS 中我们写的代码,新特性,都会编译成符合 ES5 标准的代码,自己可以尝试修改成 es2015 等值
"module": 模块导入导出的方式,默认是编译成符合 commonjs 规范的 js 代码
"outDir": 设置编译结果输出到的目录,一般设置的是 dist 目录
"rootDir": 配置源代码(TypeScript代码)所在的文件夹,一般都会放到 src 的目录
"sourceMap": 这个选项是 开启源代码映射,这样在调试的时候可以使用 sourceMap 文件去调试 ts 的源代码,非常方便
"strict": 是否严格模式,默认开启;对于类型的检查十分严格,一个变量不写注解在严格模式下是不被允许的,严格模式下需要我们为每一个成员指定一个明确的类型,不能隐式推断
. . . 其他选项暂时先不介绍 

在这里插入图片描述

这里需要注意的是:当我们去运行 tsc 指令去指定编译某个 ts 文件的时候,这个配置文件是不生效的,只有当 tsc 指令去编译整个项目的时候这个配置文件才会生效。

这里去尝试使用 tsc 去编译一下整个项目:
在这里插入图片描述
看这个报错,说 *** 文件不在指定的 rootDir 目录下,这也就是说我们的配置文件生效了。

如果想看看指定文件编译的时候配置文件是否生效,这里也可以尝试一下:
在这里插入图片描述
可以看到没报错,并且在根目录也生成了一个同名的 js 文件,说明指定编译文件的时候这个配置文件没有生效。

我们先解决编译整个项目那个报错:

新建一个 src 目录,rootDir 指定的那个目录,然后把 ts 文件移动到 src 下面,将之前生成的 js 文件就可以删除了。

再次尝试编译一下整个项目:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到,编译时没报错,并且 dist 目录下也出现了对应的文件,sourceMap 文件也出来了,并且打开编译后的对应 js 文件,会发现这个 js 文件的标准也是按照 ES2015 的标准来输出的。

TypeScript 原始类型

看一下 javascript 中的 6 种原始数据类型在 typescript 中的应用。

绝大多数的情况 同 Flow 了解的情况是类似的。

这里先把配置编译的 js 对应标准修改为常用的 es5 ,有些特殊情况这样会展示出来。
在这里插入图片描述
src 下新建一个 02-primitive-types.ts 文件


const a: string = 'foobar'

const b: number = 100 // NaN Infinity

const c: boolean = true // false

// 在严格模式下,string, number, boolean 都不能为空,在非严格模式(strictNullChecks)下,
// string, number, boolean 才可以为空
// const aa: string = null
// const bb: number = null
// const cc: boolean = null

const e: void = undefined

const f: null = null

const g: undefined = undefined

// Symbol 是 ES2015 标准中定义的成员,
// 使用它的前提是必须确保有对应的 ES2015 标准库引用
// 也就是 tsconfig.json 中的 lib 选项必须包含 ES2015
const h: symbol = Symbol()

// Promise

// const error: string = 100

stricttrue
在这里插入图片描述
strictfalse
在这里插入图片描述
这里的 strict 是开启了所有的严格模式,如果只是去检查变量不能为空的话,可以使用 strictNullChecks 这个配置项,这个选项是完全用来检查变量不能为空的。
在这里插入图片描述
还有一个 类型 void 一般我们用来指定函数返回值为空的,它的值智能是 null 或 undefined,但是在严格模式下,它只能是 undefined。

还需要注意的是 ES2015 当中新增的 symbol 类型,这个类型同样只能用来存放 Symbol() 类型的值,但是这里在使用的时候会发现这里的代码报了一个错,这个错误需看下一个知识点。

TypeScript 标准库声明

上面可以看到,当我们使用全局的 Symbol 函数去创建 symbol 值的时候,就报出了一个错误:
在这里插入图片描述
这个问题简单,只需要按照错误描述,直接将编译选项的 target 修改为 es2015 就好了
在这里插入图片描述
在这里插入图片描述
可以看到报错没有了。这里探索一下到底是为什么,将 target 修改回来 es5,再次查看,报错又出来了。

原因很简单,symbol 是 javascript 中的一个内置的标准对象,就跟之前使用的 Array Object 等性质是相同的。只不过,symbol 是 ES2015 新增的,对于这种内置的对象,它自身也是有类型的。而且这些内置的类型都在 typescript 中已经提前定义好了。

这里可以先使用 Array 然后通过右键追踪一下:
在这里插入图片描述
在这里插入图片描述

找到类型的声明文件:
在这里插入图片描述

在这个文件当中声明了所有内置对象对应的类型,那正常来讲,symbol 也应该在这个文件当中有对应的类型声明,但是再看这个声明文件的文件名 lib.es5.d.ts ,很明显,这个文件是 es5 标准库对应的声明文件,而 symbol 是 es2015 当中所定义的,所以自然不会在这个文件去定义它对应的一些类型。

看配置文件,target 是 es5 ,默认这里只会引用 es5 对应的标准库,所以代码中直接使用 es6 的 symbol 就会报错。这里不仅仅是 symbol ,任何一个在 es6 中新增的都会遇到这样一个问题。比如 es6 的 Promise,这里也会报出相同的错误:
在这里插入图片描述
要解决这种问题的办法,实际上有两种,第一种是直接修改配置文件的 target 为 es2015,这样的话默认标准库就会引用 es6 所对应的标准库,再看代码当中,这里的 Promise 就能找到。右键再次追踪一下,这时候看到的标准库文件就是 es2015 所对应的标准库文件。
在这里插入图片描述
在这里插入图片描述
如果说我们必须要编译到 es5 的话,我们也可以使用配置文件的 lib 选项去指定标准库。
在这里插入图片描述
再次查看代码种的 symbol 和 promise 就不会报错了。

但是这时候再打开 01-getting-started.js 文件,会发现 console 会报错。出现这个错误的原因是跟之前所分析的是一样的。
在这里插入图片描述
console 对象在浏览器环境当中它是 bom 对象所提供的,而我们设置的 lib 当中只有 es2015 ,所有的默认标准库都被覆盖掉了,这里需要把默认的标准库都给添加回来。不过这里需要注意的是 TypeScript 中把 BOM 和 DOM 归结到一个标准库文件当中了,就叫 DOM ;也就是这里只需要添加一个 DOM 引用就可以了。添加之后这里的 console 就不会报错了。
在这里插入图片描述
在这里插入图片描述
可以右键转到定义,就可以看到刚刚添加的 DOM 标准库文件了
在这里插入图片描述
所谓的标准库,实际上就是:内置对象所对应的声明文件;我们在代码当中使用内置对象就必须要引用对应的标准库,否则 TypeScript 就找不到对应的类型。它就会报错。

TypeScript 中文错误消息

这里是对使用中文开发者非常有帮助的小技巧,就是让 typeScript 显示中文的错误消息(我这里就是中文的)。

TypeScript 是支持多语言的,默认会根据操作系统和开发工具的设置来选择一个错误消息的语言。

这里也可以手动指定,比如使用 tsc 命令的时候跟上一个 --local 的参数,后边跟上 zh-CN ,提示的就是中文的消息。当然,也可以跟上其他语言:
在这里插入图片描述
在这里插入图片描述
对于 VSCode 当中的错误消息,可以在配置选项当中去配置:
设置 – 搜索 typescript locale 修改指定语言就可以了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我这里还是修改回来 null ,让它自己决定就行。

不过这里不推荐将错误改成中文的,因为有些错误不理解的话我们会利用搜索引擎去搜索一些相关的资料,如果看到的是中文的错误提示,那么根据这种中文的消息提示呢,是很难搜索到有用的东西的。

所以说,一般对于开发相关的东西,这里还是建议使用英文的方式。

TypeScript 作用域问题

在学习 TypeScript 的过程当中,肯定会涉及到在不同的文件当中尝试 TypeScript 不同的一些特性。这种情况下就可能会遇到不同文件当中会有相同变量名称的这样一个情况。

新建 03-module-scopes.ts 文件,在这个文件当中定义一个上一个文件当中已经存在的一个 a 变量,此时 TypeScript 就会报出一个重复变量的一个错误:
在这里插入图片描述
这是因为 a 变量目前是定义成了全局变量,目前是作用在全局作用域上面的。所以 TypeScript 在编译整个项目的时候就会出现这样一个重复定义的错误。要解决这样错误的办法自然是将他们分别装到不同的作用域当中。

比如可以使用一个立即执行的函数去创建一个单独执行的函数,这时候我们将变量放到函数的内部,这样就不会有错误了:

(function () {
    
    
  const a = 123
})()

或者我们也可以在文件当中使用 export 导出一下,也就是使用以下 es module ,这样的话,我们的文件就会作为一个模块,模块是有单独的模块作用域的:

// 模块有单独的作用域
const a = 123

export {
    
    }

在这里插入图片描述
需要注意:这里的花括号 {} 只是 export 的语法,并不是导出了一个空对象。

这样的话,在这个文件当中所有的成员,它就变成了我们这个模块作用域当中的局部成员了,也就不会再出现冲突的问题了。

这样的问题一般在实际开发中不会遇到,因为在绝大多数情况下,每个文件都会以模块的形式去工作。只不过这里在学习的过程中,每个案例或许会用到一些重复的变量,所以每个案例当中都会添加一个 export {}

TypeScript Object 类型

TypeScript 中的 Object 并不单指普通的对象类型,而是泛指所有的非原始类型。也就是对象,数组还有函数。

// Object 类型

export {
    
    } // 确保跟其它示例没有成员冲突

// object 类型是指除了原始类型以外的其它类型
const foo: object = function () {
    
    } // [] // {}

// 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
const obj: {
    
     foo: number, bar: string } = {
    
     foo: 123, bar: 'string' }

// 接口的概念后续介绍
TypeScript 数组类型

TypeScript 中定义数组的方式和 Flow 几乎一摸一样:

// 数组类型

export {
    
    } // 确保跟其它示例没有成员冲突

// 数组类型的两种表示方式

const arr1: Array<number> = [1, 2, 3]

const arr2: number[] = [1, 2, 3]

// 案例 -----------------------

// 如果是 JS,需要判断是不是每个成员都是数字
// 使用 TS,类型有保障,不用添加类型判断
function sum (...args: number[]) {
    
    
  return args.reduce((prev, current) => prev + current, 0)
}

sum(1, 2, 3) // => 6
TypeScript 元组类型

元组类型是一种特殊的数据结构,其实元组就是一个明确元素数量以及明确元素类型的一个数组,各个元素类型不必要完全相同,在 TypeScript 当中可以使用类似数组字面量的这种语法去定义元组类型:

// 元组(Tuple)

export {
    
    } // 确保跟其它示例没有成员冲突

const tuple: [number, string] = [18, 'zce']

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

const [age, name] = tuple

// ---------------------

const entries: [string, number][] = Object.entries({
    
    
  foo: 123,
  bar: 456
})

const [key, value] = entries[0]
// key => foo, value => 123
TypeScript 枚举类型

在应用开发过程中经常需要用某几个数值去代表某种状态,如果我们直接在代码中使用数字这种字面量表示某种状态的话,时间久了我们就可能搞不清楚我们这里写的数字代表的是什么,而且时间久了,还有可能混进来一些其他的值。

这种情况下我们使用枚举类型是最合适的了。

因为枚举类型有两个特点:

  1. 它可以给一组数值分别起上一个更好理解的名字;
  2. 一个枚举中,只会存在几个固定的值,并不会出现超出范围的可能性

在很多传统的编程语言中,枚举是一种非常常见的类型,不过在 javascript 中,并没有这种数据结构,很多时候我们需要使用对象去模拟实现枚举。

// 用对象模拟枚举
const PostStatus = {
    
    
  Draft: 0,
  Unpublished: 1,
  Published: 2
}

这样的话我们就可以在代码中去使用对象当中的属性去表示文章的状态,这样在使用过程当中就不会出现上面所说的问题。

而现在 TypeScript 当中有一个专门的枚举类型(enum),可以使用 enum 去声明一个枚举:

// 标准的数字枚举
enum PostStatus {
    
    
  Draft = 0,
  Unpublished = 1,
  Published = 2
}

注意:这里使用的是等号( = ),而不是对象当中的冒号( : )

使用方式同使用对象是一样的。

const post = {
    
    
  title: 'Hello TypeScript',
  content: 'TypeScript is a typed superset of JavaScript.',
  status: PostStatus.Draft // 3 // 1 // 0
}

需要注意的是这里的枚举值可以不用 等号 去指定,如果不去指定的话,这里的值默认是从0开始累加,如果我们给枚举的第一个成员指定了一个值(数字类型),那么后面的成员值会在这个值的基础上进行累加。

const enum PostStatus {
    
    
  Draft,
  Unpublished,
  Published
}
// 数字枚举,枚举值自动基于前一个值自增
enum PostStatus {
    
    
  Draft = 6,
  Unpublished, // => 7
  Published // => 8
}

枚举的值除了是数字以外,还可以是字符串,也就是字符串枚举,就是初始化的时候把每个值初始化为字符串,由于字符串是无法像字符串一样去自增长的,所以说,如果是字符串枚举的话,就需要手动的给每一个成员指定一个明确的字符串作为值。

// 字符串枚举
enum PostStatus {
    
    
  Draft = 'aaa',
  Unpublished = 'bbb',
  Published = 'ccc'
}

字符串枚举一般并不常见。

关于枚举我们还需要了解一个内容就是枚举类型会入侵到我们运行时的代码,通俗来讲就是会影响我们编译后的结果。

我们在 TypeScript 当中使用的大多数类型,经过编译转换之后都会移除掉,因为它只是在编译过程当中可以做类型检查,而枚举它不会,它最终会编译成一个双向的一个键值对对象,这里可以编译一下看看:
在这里插入图片描述
所谓的双向的键值对对象,其实就是可以通过键去获取值,也可以通过值去获取键;
通过观察就会发现,这里其实就是把枚举的名称作为键去存储了值,然后再用值作为键,去存储了枚举当中的键。
在这里插入图片描述
这样做的目的是可以通过动态的枚举值去获取枚举的名称,也就是根据 0,1,2 … 这样的值去获取枚举的名称,也就是在代码当中可以通过索引器的方式去动态的获取枚举名称:

PostStatus[0] // => Draft

如果说我们确定代码中不会使用索引器的方式去访问枚举,那么建议大家使用 常量枚举 ,常量枚举的用法就是在枚举的 enum 前面加上一个 const,加上 const 之后可以重新运行编译看一下编译后的代码:

// 常量枚举,不会侵入编译结果
const enum PostStatus {
    
    
  Draft,
  Unpublished,
  Published
}
const post = {
    
    
  title: 'Hello TypeScript',
  content: 'TypeScript is a typed superset of JavaScript.',
  status: PostStatus.Draft // 3 // 1 // 0
}

在这里插入图片描述
这里就会发现我们所使用的枚举被移除掉了,而我们所使用枚举值的地方,就会被替换成原本枚举的数值。那枚举的名称,会在后面以注释的方式被标注。

TypeScript 函数类型

函数的类型约束:

无外乎是对函数的输入输出进行类型限制,输入指的是参数,输出指的是返回值。

不过在 javascript 中有两种函数定义的方式,分别是 函数声明 和 函数表达式,所以这里需要了解下在两种声明下,函数如果进行类型约束:

// 函数类型

export {
    
    } // 确保跟其它示例没有成员冲突
/* 可选参数:a?: number 或者 a: number = 10 必须出现在最后 */
function func1 (a: number, b: number = 10, ...rest: number[]): string {
    
    
  return 'func1'
}

func1(100, 200)

func1(100)

func1(100, 200, 300)

// -----------------------------------------

const func2: (a: number, b: number) => string = function (a: number, b: number): string {
    
    
  return 'func2'
}

TypeScript 任意类型

由于 javascript 自身是弱类型的关系,javascript 有很多内置的 api 本身就支持接收任意类型的参数,而 TypeScript 又是基于 javascript 之上的,所以说 难免会在代码中用一个变量去接收任意类型的数据。

any 就是可以用来接收任意类型数据的一种类型。

需要注意的是 any 类型仍然属于动态类型,它的特点跟普通的 javascript 是一样的,也就是可以用来去接收任意类型的值,而且在运行过程当中,它还可以去接收其他类型的值。

正是因为它有可能去存放任意类型的值,所以 typescript 不会对 any 这种类型做类型检查。这也就意味着我们仍然可以像之前在 javascript 中一样,在它上面去调用任意的成员,语法上都不会报错。这里仅仅指的是语法上都不会报错。

也正是因为 any 类型不会去做任何的检查,所以它仍然会存在类型安全的问题,所以说轻易不要去使用 any 类型。

但是有的时候去兼容一些老的代码的时候,可能难免会用到这样一个类型。

// 任意类型(弱类型)

export {
    
    } // 确保跟其它示例没有成员冲突

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

stringify('string')

stringify(100)

stringify(true)

let foo: any = 'string'

foo = 100

foo.bar()

// any 类型是不安全的
TypeScript 隐式类型推断

在 TypeScript 中,如果我们没有明确使用类型注解去标记一个变量的类型,TypeScript 会根据这个变量的使用情况,去推断这个变量的类型,这样的一种特性,就叫做隐式类型推断。

虽然在 TypeScript 当中支持隐式类型推断,而且这种隐式类型推断可以帮我们简化一部分代码,但是这里仍然建议大家给每一个变量添加一个明确的类型。因为这样便于后期能更直观的理解我们的代码。

// 隐式类型推断

export {
    
    } // 确保跟其它示例没有成员冲突

let age = 18 // number

// age = 'string'

let foo

foo = 100

foo = 'string'

// 建议为每个变量添加明确的类型标注
TypeScript 类型断言

在某些特殊的情况下,TypeScript 无法去推断出来一个变量的具体类型,而作为开发者我们可以根据代码的使用情况,我们可以明确的知道这个变量到底是什么类型的。

这种情况下,我们就可以去断言这个变量是某种类型的。断言的意思就是明确告诉 TypeScript ,你相信我,我们这个地方一定是 某某类型的。

类型断言的方式有两种:

  1. as 关键词 variable as number
  2. 尖括号的方式断言 <number> variable

但是这种尖括号的方式有一个小问题,当我们代码中使用了 jsx 的时候,比如 react ,尖括号会跟 jsx 语法上的标签产生语法冲突,这样的话这种情况下就不能使用这种方式了。所以说一般没啥事儿就不要用尖括号语法了,统一使用 as 语法就好。

需要注意的是类型断言并不是类型转换,也就是说这里不是把一个类型转换成了另外一个类型,因为类型转换是代码在运行时的一个概念,而类型断言的地方呢,只是在编译环节的一个概念,当代码编译过后,这个断言也就不会存在了。

// 类型断言

export {
    
    } // 确保跟其它示例没有成员冲突

// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]

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

// const square = res * res

const num1 = res as number

const num2 = <number>res // JSX 下不能使用
TypeScript 接口

interfaces: 接口,可以理解成一种规范,或者说契约。

它是一种抽象的概念,可以用来约定对象的结构,我们去使用一个接口呢,就必须要去遵守这个接口全部的约定。

在 TypeScript 中,接口最直观的体现呢,就是它可以用来约定一个对象当中具体应该有哪些成员,而且这些成员的类型应该是什么样的。

// 接口

export {
    
    } // 确保跟其它示例没有成员冲突
/*注意这里的语法,可以使用逗号来分割,也可以使用分号来分割,也可以省略,根据个人编码习惯*/
interface Post {
    
    
  title: string
  content: string
}

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

printPost({
    
    
  title: 'Hello TypeScript',
  content: 'A javascript superset'
})

一句话总结:

接口是用来约束对象的结构,一个对象去实现一个接口,就必须要去拥有接口当中所拥有的所有成员。

TypeScript 中的接口,只是用来为我们有结构的数据进行类型约束的,在实际运行阶段,这种类型的接口并没有任何的意义。

TypeScript 接口补充

对于接口约定的成员,还有一个特殊的用法:

// 可选成员、只读成员、动态成员

export {
    
    } // 确保跟其它示例没有成员冲突

// -------------------------------------------

interface Post {
    
    
  title: string
  content: string
  subtitle?: string
  readonly summary: string
}

const hello: Post = {
    
    
  title: 'Hello TypeScript',
  content: 'A javascript superset',
  summary: 'A javascript'
}

// hello.summary = 'other'

// ----------------------------------

interface Cache {
    
    
  [prop: string]: string
}

const cache: Cache = {
    
    }

cache.foo = 'value1'
cache.bar = 'value2'
TypeScript 类的基本适用

类,是面向对象编程中一个最重要的概念,类的作用就是用来描述一类具体事务的抽象特征。

站在程序的角度,类,可以用来描述一类具体对象的抽象成员。在 ES6 以前,是通过函数+原型,去模拟实现的类,从 ES6 开始呢,javascript 就有了专门的 class ,而在 TypeScript 中,我们除了可以使用所有 ECMAScript 标准当中所有类的功能,它还添加了一些额外的功能和用法。

例如对类的成员呢有特殊的访问修饰符,还有一些抽象类的概念。

// 类(Class)

export {
    
    } // 确保跟其它示例没有成员冲突

class Person {
    
    
  /*成员必须要有一个初始值,1是声明的时候直接赋值,2是在constructor里边去赋值,两者取其一*/
  name: string // = 'init name'
  age: number
  
  constructor (name: string, age: number) {
    
    
    this.name = name // 不能动态直接添加,需提前声明
    this.age = age
  }

  sayHi (msg: string): void {
    
    
    console.log(`I am ${
      
      this.name}, ${
      
      msg}`)
  }
}
TypeScript 类的访问修饰符

这里看几个 TypeScript 中类的特殊用法:
对于构造函数的访问修饰符,默认是 public ,如果设置为 private,就不能在类外去实例化,只能通过在类的内部创建一个静态方法,在静态方法内部去返回实例化这个类的对象。

// 类的访问修饰符

export {
    
    } // 确保跟其它示例没有成员冲突

class Person {
    
    
  public name: string // = 'init name' 公有的,类里类外子类都能访问
  private age: number // 私有的,类里类外子类都能访问
  protected gender: boolean // 受保护的,只有自身和子类能访问
  
  constructor (name: string, age: number) {
    
    
    this.name = name
    this.age = age
    this.gender = true
  }

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

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)

const jack = Student.create('jack', 18)
TypeScript 类的只读属性

还可以通过 readonly 把这个成员设置为只读,注意:如果当前成员有了访问修饰符的话,readonly 只能跟在访问修饰符的后面:

// 类的只读属性

export {
    
    } // 确保跟其它示例没有成员冲突

class Person {
    
    
  public name: string // = 'init name'
  private age: number
  // 只读成员
  protected readonly gender: boolean
  
  constructor (name: string, age: number) {
    
    
    this.name = name
    this.age = age
    this.gender = true
  }

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

const tom = new Person('tom', 18)
console.log(tom.name)
// tom.gender = false
TypeScript 类与接口

相比于类,接口的概念会更为抽象一点。

不同的类与类之间,也会有一些共同的特征,对于这些共同的特征,我们一般会使用接口去抽象。

需要注意的是:在 C# 和 java 这样的一些语言当中,它建议我们尽可能让每个接口的定义更加简单,更加细化。

// 类与接口

export {
    
    } // 确保跟其它示例没有成员冲突

// 这里需要让接口尽可能更加简单,更加细化,所以一个接口分成了下方的两个接口
// interface EatAndRun {
    
    
//   eat (food: string): void
//   run (distance: number): void
// }

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

interface Run {
    
    
  run (distance: number): void
}

class Person implements Eat, Run {
    
    
  eat (food: string): void {
    
    
    console.log(`优雅的进餐: ${
      
      food}`)
  }

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

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

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

这里说一句题外话:

大家千万不要把自己框死在某一门语言或者是技术上面,最好可以多接触,多学习一些周边的语言或者技术,因为这样的话可以补充你的知识体系。

最简单来说,一个只了解 javascript 程序员,即便他有多么多么的精通,他也很难设计出来一些比较高级的产品。

比如现在有很多的框架采用的 MVVM 思想,这样的思想其实最早出现的是微软的 WPS 当中的,如果说你有更宽的知识面的话,你可以更好的把多家的思想融合到一起。

TypeScript 抽象类

抽象类在某种程度上来说跟接口比较类似,它也是用来去约束子类当中必须要有某一个成员,但是不同于接口的是抽象类它可以包含一些具体的实现,而接口它只能是一个成员的抽象,它不包含具体的实现。

一般比较大的类目都建议使用抽象类。

只能去继承,不能通过 new 关键词去得到一个实例对象了。

// 抽线类

export {
    
    } // 确保跟其它示例没有成员冲突

abstract class Animal {
    
    
  eat (food: string): void {
    
    
    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('嗯西马')
d.run(100)
TypeScript 泛型

泛型是指在定义函数、接口或类的时候没有去指定具体的类型,等到使用的时候再去指定具体的类型的这样一种特征。

比如函数:在声明的时候不去指定一个具体的类型,等到调用的时候才去指定一个具体的类型。

这样做的目的就是极大程度去复用我们的代码。

// 泛型

export {
    
    } // 确保跟其它示例没有成员冲突

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)
// res => [100, 100, 100]

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

泛型就是把我们定义的时候不能明确的类型定义成一个参数,让我们在使用的时候再去传递这个参数。

TypeScript 类型声明

实际开发过程中不可避免的会用到一些第三方的 npm 模块,而这些第三方的模块呢,并不是都是通过 typescript 编写的,所以说它所提供的成员呢就不会有强类型的体验。

这里以 lodash 为例:

npm install --save lodash

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

说白了就是一个成员在定义的时候因为种种原因没有声明一个明确的类型,然后我们在使用的时候可以单独为它做出一个明确的声明。

这种用法存在的原因就是考虑为了兼容一些普通的 js 的模块,由于 typescript 的社区非常强大,目前一些绝大多数比较常用的 npm 模块,都已经提供了对应的声明,我们只需要安装一下它所对应的类型声明模块就可以了;
在这里插入图片描述
这里的错误信息描述已经给了一个明确的提示 npm install @types/lodash

这个模块其实就是一个对应的 ts 的类型声明模块,这里去安装一下。

需要注意的是类型声明模块它应该是一个开发依赖,因为它里边不会提供任何具体的代码,只是对一些模块做一些对应的类型声明,安装完成过后,这里的 lodash 模块就会有对应了类型提示了:

npm install --save-dev @types/lodash

在这里插入图片描述
在这里插入图片描述
但是现在越来越多的第三方模块已经在内部集成了这种类型声明的文件,很多时候我们都不需要单独的安装这种类型声明模块,例如 query-string 这样的模块,它就已经直接包含了所对应了类型声明文件。

npm install --save query-string
// 类型声明

import {
    
     camelCase } from 'lodash'
import qs from 'query-string'

qs.parse('?key=value&key2=value2')

// declare function camelCase (input: string): string 

const res = camelCase('hello typed')


export {
    
    } // 确保跟其它示例没有成员冲突

总结:在 TypeScript 中如果我们引用第三方的模块,如果这个模块当中不包含所对应的类型声明文件,我们就可以去尝试安装一个所对应的类型声明模块(@types/模块名),如果说也没有这样的一个对应的类型声明模块,这种情况下我们就只能自己使用 declare 语句去声明其所对应模块的类型。

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)

【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)

【Part1作业】https://gitee.com/zgp-qz/part01-task

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)

【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)

【Part2作业】https://gitee.com/zgp-qz/part02-homework

猜你喜欢

转载自blog.csdn.net/qq_38652871/article/details/108871729