从类型安全角度区分类型。
从类型检查角度区分
JavaScript 既是弱类型又是动态类型,非常自由任性,没有任何限制
JavaScript 是脚本语言,没有编译环节直接执行,也就没有编译环节去检查类型,而静态语言要先经过编译再执行。
而在现如今 JavaScript 大规模使用,就会暴露一些弱类型,动态类型的问题
let obj = {
}
obj.handle() // obj中没有该属性方法,只有当代码运行才会报出异常
function sum (a, b) {
return a + b
}
console.log(sum(100, 100))
console.log(sum(100, '100')) // 此时变成了字符串拼接
const obj = {
}
obj[true] = 100 // 属性名会自动转换为字符串
console.log(obj['true'])
相比于弱类型语言,强类型语言有以下的优势:
Flow 是 JavaScript 的静态类型检查器,Flow 发布在 GitHub 上. 在 Facebook 内部大量使用,并且是开源的.
Flow 使用类型注解,来给参数添加类型控制。在生产环境中可以通过编译工具,去掉类型注解,我们可以在自己需要的地方添加类型注解
npm i flow-bin -D
// @flow
标记文件要被flow进行检查"flow": "flow"
npm run flow init
npm run flow
方案一:自动移除类型注解,官方提供的模块:
npm i flow-remove-types --dev
安装flow-remove-types模块"flow-remove-types":"flow-remove-types . -d dist"
npm run flow-remove-types
生成dist目录,其中的文件就是编译后的。方案二:babel
安装babel npm i @babel/core @babel/cli @babel/preset-flow -D
创建.babelrc文件
{
"presets": [
"flow"
]
}
在package.json的script 中添加 "build":"babel . -d dist"
运行 npm run build
安装VSCode插件来实时监听类型错误,但是需要保存之后才能看到错误的波浪线
/**
* 类型注解
*
* @flow
*/
function square (n: number) {
return n * n
}
let num: number = 100
// num = 'string' // error
function foo (): number {
// 标记函数的返回值是number
return 100 // ok
// return 'string' // error
}
function bar (): void {
// 标记函数没有返回值
// return undefined
}
/**
* 数组类型
*
* @flow
*/
const arr1: Array<number> = [1, 2, 3] // 写法一
const arr2: number[] = [1, 2, 3] // 写法二
// 元组
const foo: [string, number] = ['foo', 100] // 数据结构需要跟左侧结构一致
/**
* 对象类型
*
* @flow
*/
const obj1: {
foo: string, bar: number } = {
foo: 'string', bar: 100 }
const obj2: {
foo?: string, bar: number } = {
bar: 100 } // ? 表示可选
const obj3: {
[string]: string } = {
} // 表示允许任意个数的键 但是必须要是string
obj3.key1 = 'value1'
obj3.key2 = 'value2'
/**
* 函数类型
*
* @flow
*/
// 限制foo函数只能接收一个回调函数作为参数
// 并且这个回调函数的参数必须是string 和 number, 没有返回值
function foo (callback: (string, number) => void) {
callback('string', 100)
}
foo(function (str, n) {
// str => string
// n => number
})
const a: 'foo' = 'foo'
a的值只能是foo字符串const type: 'success' | 'warning' | 'danger' = 'success'
type的值只能是三者之一 type StringOrNumber = string | number
const b: StringOrNumber = 'string' // 100
// gender的值 也许是数字也可以是undefined
const gender: ?number = undefined
function passMixed (value: mixed) {
if (typeof value === 'string') {
value.substr(1)
}
if (typeof value === 'number') {
value * value
}
}
而any 就是随便使用类似于js原始的类型特性 function passAny (value: any) {
value.substr(1)
value * value
}
- TypeScript可以编译出纯净、 简洁的JavaScript代码,并且可以运行在任何浏览器上、Node.js环境中和任何支持ECMAScript
3(或更高版本)的JavaScript引擎中- 类型允许JavaScript开发者在开发JavaScript应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构
- TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自2015年的ECMAScript和未来的提案中的特性
以上这些是ts的优势,但是ts也存在缺陷:
ts 可以安装到全局,也可以在项目中安装
npm install -g typescript
全局安装tstsc 文件名.ts
就可以自动生成编译好的js文件npm i typescript -D
“tsc”:"tsc"
npm run tsc 文件名.ts
运行 yarn tsc --init
创建tsconfig.json 配置文件。
我们来了解几个简单的配置项含义
ts 中的原始数据类型与flow类似 ,但是根据strict设置不同,也有一些差异。
string 在严格模式下 不能为null或者undefined
boolean 在严格模式下 不能为null或者undefined
number 在严格模式下 不能为null或者undefined
null
undefined
symbol 使用symbol要注意把target设置为ES2015, 因为ES2015之前是没有symbol类型的
void 表示一个函数没有返回值,严格模式下默认值是undefined
这里我们需要解决一个标准库的问题,因为如果在编码中使用了非当前标准库的语法那么TS就会报错,我们可以通过修改ts配置文件中的lib 属性来添加标准库
// 如果只设置ES2015就会覆盖掉默认标准库导致其他环境api报错,所以还需要引入不同环境下的标准库比如DOM
// typeScript中把BOM和DOM归结到一个标准库DOM当中了,所以只需要添加一个DOM
"lib": ["ES2015","DOM"]
标准库就是内置对象所对应的声明,我们在代码中使用内置对象就要使用对应的标准库,否则typeScript就找不到对应的类型,就会报错
在同一目录下的不同ts文件中,如果声明的变量重名了,会报错重名。这是作用域的问题,因为这些变量都是声明在全局上的,所以会提示错误。
object 类型,并不是限制只能传入对象,而是指所有非原始数据类型之外的类型,可以是函数也可以使数组也可以是对象
// Object 类型
export {
} // 确保跟其它示例没有成员冲突
// object 类型是指除了原始类型以外的其它类型
const foo: object = function () {
} // [] // {}
// 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
const obj: {
foo: number, bar: string } = {
foo: 123, bar: 'string' }
const arr1: Array = [2,3,4]
const arr2: number[] = [1, 2, 3]
假设我们定义一个函数 接收不固定数量的参数,要求这些参数都是数字类型。如果是在js中我们可能需要添加参数类型的判断,而如果使用ts 就会简单很多
function fn(...args: number[]){
……… }
元组就是明确元素数量以及每个元素类型的数组
// 元组就是明确元素数量以及每个元素类型的数组
// 限制tuple的值只能是长度为2 第一个成员类型为数字,第二个成员类型为字符串的结构
const tuple: [number, string] = [1,'a']
const [num, str] = tuple // num-1 str-'a'
export {
}
const enum Status{
Default = 0,
Success = 1,
Fail = 2
}
// 没有设置初始值的情况下,默认为第一个元素赋值为 0,后面的元素会在第一个元素的值上递增
enum Status2{
Default,// 0
Success,// 1
Fail// 2
}
// 没有设置初始值的情况下,默认从 0 开始
enum Status3{
Default = 6,// 6
Success,// 7
Fail// 8
}
a?
或者 函数默认值 b: number=10
,不确定数量的可以用ES6的…argsexport {
} // 确保跟其它示例没有成员冲突
// 参数数量在调用时 必须保持一致
function func1 (a: number, b: number = 10, ...rest: number[]): string {
return 'func1'
}
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
return 'func2'
}
使用 any 接收任意类型参数,依然是动态类型。不会有类型检查,依然有类型安全问题
const a: any = 'abc'
如果我们没有给一个变量添加类型注解,那么TS会根据这个变量的使用,去推断它的类型。建议还是给每一个都添加类型注解
在某些情况下 TS 无法在编译过程中 知道一个运算的值是什么类型,从而导致后续的代码编译时报错
那么此时我们可以使用断言 as 来确定类型
export {
} // 确保跟其它示例没有成员冲突
// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]
const res = nums.find(i => i > 0)
// const square = res * res 这里会提示报错 因为ts 认为res是number|undefined
const num1 = res as number
const num2 = <number>res // JSX 下不能使用
接口可以用来约定一个对象的结构,我们使用一个接口,那么就要遵守这个接口全部的约定
export {
}
interface person {
name: string
age: number
gender: string
}
// p 这个参数必须要有person接口中的结构
function func(p: person){
console.log(p.name);
}
// 可以添加任意属性名为string类型 值为string类型的属性
interface Cache {
// prop 只是一个象征意义上的名字
[prop: string]: string
}
const cache: Cache = {
}
cache.foo = 'value1'
cache.bar = 'value2'
ts 中类的使用与js中ES6中新增的class 差不多,略有差异。 主要体现在类属性定义上,ts的class中的类属性在使用之前,必须要先声明。
export {
}
class Pers {
name: string
age: number
constructor(name: string,age: number){
this.name = name
this.age = age
}
// 定义方法
sayHi( msg: string ):void {
console.log( `hello,I'm ${
this.name }, ${
msg }` )
}
}
如果constructor添加了private 那么是不能直接在类外部new出实例的,可以在静态方法中来调用new创建实例,并return
class Student {
public name: string
public age: number
private constructor (name: string, age: number) {
this.name = name
this.age = age
}
static create (name: string, age: number) {
return new Student(name, age)
}
}
const jack = Student.create('jack', 18)
添加只读属性,class中添加只读属性跟接口中类似 都是通过readonly来实现,具体语法就是给在属性修饰符之后 属性名之前 添加readonly public readonly name: string
ts中提到的接口,跟我们前端开发中经常提到的接口请求的接口,有一定差别。
很多只接触过JavaScript的朋友对于接口的理解,可能是我请求这个接口地址就能获得对应的数据
,而我们ts这里提到的接口,是强类型语言中的一个概念,主要是用来实现多态。
而多态在js中并不需要特地去实现 因为js本身就是弱类型的,它天生就有多态的能力。
ts中的接口是比类更加抽象的一种概念,可以理解为是多个类它们之间共性的一种约定,类实现(implements)了某个接口 那么它的内部就必须拥有这种接口的能力。
举个简单的例子
现在有两个类
一个是人类
一个是动物类
人类 和 动物类 都拥有可以吃 可以跑的 两个能力
所以我们可以把它们共性的能力抽离出来,形成一个 EatandRun 的接口 这里接口里就提供两个能力 eat方法 和 run 方法
那么实现该接口的 人类 和动物类 当中则必须要有这两个方法
那么,问题又来了,有人可能会问 那为什么我不直接封装一个父类 而要使用接口呢?
问的好!这里的人类 和 动物类 他们都有eat 和 run 方法 但是他们两个类 实现eat 和 run 的方式是不一样的,
同样是 eat 方法 人类 的eat 方法 是要把食物做熟吃,动物类是生吃
所以如果是一个父类继承 那么这两个方法的实现方式就被固定了,而接口 只是约束了它们有这样的能力 ,具体的实现 可以由各自类的内部 自由实现
export {
} // 确保跟其它示例没有成员冲突
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}`)
}
}
抽象类更像是接口跟父类的结合,既可以在其中定义具体的方法,也可以定义抽象方法交给子类去自由实现
抽象类是用来继承的,不能被实例化。抽象类里可以有成员变量,接口中没有。
定义一个抽象类,使用abstract class两关键字定义
// 抽线类
export {
} // 确保跟其它示例没有成员冲突
abstract class animate {
uname: string
constructor(uname: string) {
this.uname = uname
}
// 抽象方法 不需要函数体
abstract eat(msg: string): void
// 普通方法
run(): void {
console.log(`${
this.uname} is run`);
}
}
class Dog extends animate {
age: number
constructor(uname: string, age: number) {
super(uname)
this.age = age
}
eat(msg: string) {
console.log('狗狗吃' + msg);
}
}
let ww = new Dog('viki',2)
console.log(ww.uname);
ww.run()
ww.eat('肉')
不确定函数的参数类型 或者返回值类型 可以使用泛型。
T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型
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')
在开发过程中不可避免要引用其他第三方的 JavaScript 的库,虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。
一般的第三方js插件在npm上都有对应的声明文件, 比如lodash的声明文件就可以在npm上下载 npm i @types/lodash
这样使用的时候导入.
以.d.ts结尾的就是声明文件,这样就不会再报找不到模块声明了
如果第三方插件没有对应的声明文件 我们可以自己去为它的成员添加类型声明 从而添加类型检查
import {
camelCase } from 'lodash'
declare function camelCase (input: string): string
const res = camelCase('hello typed')