前言
TS 是 JS 的一个超集,保留JS原有语法和动态特性的基础上,添加可选静态类型和基于类的面向对象编程。
1. JS的面向对象编程
JavaScript 是基于函数实现面向对象编程的。
JS的设计者在最初就选择了,和当时主流语言JAVA / C++ 等“基于类的模型”不同的技术路线—— “基于原型的面向对象”,让对象直接从其他对象中继承,而不需要抽象出“类”作为中介。
1.1 面向对象实现:
1. JS 创建构造函数
- 函数拥有一个prototype属性(JS引擎内部进行的初始化动作)
- prototype 属性会指向另一个对象 :Person.prototype.publicFun
构造函数角度:构造函数 的 公共仓库Prototype 中的资源publicFun
实例对象角度:实例对象的原型prototype的对象(隐式父对象)2. 通过 new 创建实例
- 创建一个新的空对象
- 该实例对象的内部指针__proto__执行构造函数的prototype对象
- 执行构造函数,并将函数体的this绑定到这个新的对象中
- 返回上述新创建的对象
prototype:用于存放实例 公共行为资源 的仓库,减少内存浪费
// Person.prototype.PublicFun = function(){} var Person = new Function() Person.prototype ={ construct: Person; publicFun: function(){console.log("自定义公共行为")} }原始数据结构的原型属性列举:
String:length,slice,split,trim,indexOf,replace
Array:length,push,pop,map,filter,forEach(内部封装迭代器),concat
ES6:
Map: size, set(key, value), get(key), has(key), delete(key), clear(), keys(), values(), entries()Set: add(value)
2. TS 的核心机制/规则
可选静态类型 和 基于类的面向对象编程
可选静态类型: 编译阶段对变量的类型进行检查,可以通过定义类型判断,也可以通过赋值自动推导,弥补JS动态类型在复杂协作和项目上造成的负担。
为什么理论上TS定义参数要添加类型,但是实际可不写(已赋值)/ 可写(初始化 或 类型为any)?
值得一提 TS 的核心优势之一 “类型推断”。
那么,“类型推断”的核心价值?
1. 优化 JS 这种动态类型语言中,参数类型在开发中可任意改变的灵活性,在复杂开发中易产生参数错乱的情况,从而对参数类型严格监控。
2. 简化 TS 代码的编写,提升代码的可读性与维护性。
注意:编译时进行类型推断,如果从接口获取了不合规的数据,TS 的推断无法在浏览器运行时帮助拦截报错的,需要配合运行时的校验库(如 Zod 等)
TypeScript严格模式(strict mode)配置:
"strict": true或者单独开启"noImplicitAny": true
基于类的面向对象编程: 对比JS的动态类型,且在运行时可以给原型添加属性的特点;类的语法明确定义了属性、方法、访问权限等;所以,TS编译器分析阶段 ,根据类固定的结构提取出类型信息配合可选静态类型进行严格检查,检查通过后,编译为JS的function,才能在浏览器中运行。
3. TS基础数据结构
类型的声明、类型的集合
3.1. 基础类型
boolean | string | number |array | null |undefined
let flag: boolean = true let course: string = 'caorui' let grade: number = 98 // undefined 和 null 只能赋值给自己或者any let u: undefined = undefined let friend: null = null let classArr: string[] = ['basic','execute'] let classArr1: Array<string> = ['basic','execute']tuple 元组
tuple 有限个数和有限类型
type 无限集
enum 枚举
默认是数据类型枚举:从零开始,依次递增
声明变量 any | unknow
声明函数的返回值 void | nerver
3.2. 复合类型
将多个值组合成一个复杂的结构。
3.2.1. 对象类型的对比
object | Object | {}
(1)object 引入非原始类型,对象自己携带了属性
通用对象的属性,如 .hasOwnProperty / .toString / .valueOf,类型检查通过;
如果遇到自定义的业务属性,TS 会因为不认识,将这些属性禁用,所以此时就需要用到 type/interface创建类型别名,使得业务属性通过TS的类型检查
// 包含普通对象{}, 数组,函数,类实例…… let obj: object; obj = { name: "Alice" }; obj = [1, 2, 3];(2)Object 原始类型 + 非原始类型:继承 JavaScript 中对象构造函数 以及原型链 的类型
(3){} 能接受原始类型,但是不能访问属性。
// 联合类型的限制作用:{} 限制 value 不能是 null 或 undefined function valueData<T extends {}>(value: T) { console.log(value); }3.2.2 核心类型系统:interface 和 type 的理解
3.2.2.1. interface实际意义
- 核心定位
定义抽象接口,将核心业务逻辑和外部依赖细节隔离,即业务做什么和怎么实现分离。
符合“依赖倒置原则(Dependence Inversion Principle)”
// 编译阶段:只做结构校验 interface IStorage{} // 强制类实现接口定义的结构 class LocalStorage implements IStorage{}- 持久化存储解耦为例
// “单例模式” + “类型导出”模式 export type SessionToken = { accessToken: string, refreshToken: string, expires: number, } // 对外暴露相同的方法和属性 export interface StorageLike{ setItem(key: string, value: string): void getItem(key: string): string | null removeItem(key: string): void } // 业务逻辑:依据interface契约操作 export class TokenStore{ private storage:StorageLike constructor(storage:StorageLike){ this.storage = storage } } function anotherStorage:StorageLike{} const definedStore = typeof window !== 'undefined' && window.localStorage ? window.localStorage : anotherStorage export const tokenStore = new TokenStore(definedStore)思考:
1. 对比使用 type 和 interface 定义对象类型的差异
2. 解耦思想实际应用场景:持久化存储;网络场景;时间处理等
3. 内置数据结构类型有哪些:
TS 继承 JS 的内置对象,并添加了强类型支持。比如:TS 给 JS 的 Set 和 Map 增加了泛型支持
Set集合(ES6):存储唯一值,去重
Map映射(ES6):存储键值对,键值可以是任何类型
TS的内置工具类型:Pick、Record、Partial、Omit 等。
自定义高级数据结构有哪些:
结合 接口 + 泛型 实现复杂的业务数据结构// 定义通用数据结构(接口契约) interface PageRes<T>{ list: T[], total:number}
5. 内置的工具类型
Pick: 从一个已有类型里,挑出指定的几个字段/属性,组成一个新的类型。
// Storage —— Web Storage API type StorageType = Pick<Storage, 'getItem'|'setItem'|'removeItem'>6. 类型的计算
通过基础类型计算出复杂的数据结构
- 联合类型:相当于“或”运算,允许数据有多种形态。
- 交叉类型:相当于“与”运算,将多个结构合并为一个。
存在一些变量是多种不同类型的合,比如象棋的颜色/身份,所以需要对多种类型进行合并—— 联合类型
联合类型的存在,让TS编译器在运行到某一行代码时,无法确认变量的类型是 —— 引申出类型守卫(in,instanceof,typeof),需要对变量类型进行判断,保证多种类型中选择了合适的一个。
类型守卫和断言配合使用。类型守卫针对不确定的情况,增加一层判断保障;断言针对对系统非常熟悉的情况,强行明确变量的类型(as,!(),尖括号),减少其他繁琐操作。它们结合使用,确保TS编译阶段的类型检查工作。
7. 类型进行动态化
泛型 接收 类型参数,像函数传变量值一样动态操作变量类型,并返回具体的数据结构。
函数存在形参和实参,JavaScript中的实参只有在函数真正运行时才会知道实际参数,而JS的形参 fn(10) 是无类型的,所以经常写 typeof 进行检查。
而 TypeScript 的 泛型,针对JS中存在的问题进行了完善。泛型 通过 显式传递 / 隐式传递 将 形参的类型传递进来,使得函数在运行前就已经知道参数的类型。
function startClass<T,U>(name:T, score:U): T{ return name + score } startClass<string,number>('ts',5)思考:TS的语法糖装饰器(Java中的注解)