数据响应式
- 侵入式:react,小程序
- 非侵入式: vue
区别:侵入性指必须使用独特的语法、数据类型,基于某个库的api,数据响应的方式比较直观; 非侵入性例如vue,使用最普通的js对象,所做的只是读取、赋值对象的某个属性,代码上并没有体现出mvvm的实现逻辑
vue2数据劫持的问题
(1) 只能拦截对象属性的 get 和 set 操作
(2) 动态添加新属性(响应式丢失)
- 初始化
data时进行定义 - $set
- 同时赋值多个属性使用:
Object.assign({}, this.obj, { a: 1, b: 2 })
(3) 通过 delete 删除属性(响应式丢失)
- $delete
(4) 使用数组索引替换元素/修改数组长度(响应式丢失)
- splice/$set
(5) 数组变更,如push、pop、splice...(响应性丢失)
- 对方法进行重写(更改数组的原型链,指向重写后的数组原型方法),保证能检测更新
(6) 数据结构复杂时(属性值为引用类型数据),需要递归进行处理
(7) 一次只能对一个属性实现数据劫持,需要遍历对所有属性进行劫持
特别地:vue3监听数组变更(push、pop、splice...)使用deep
vue2为什么不检测数组的变动
主要原因
- 数组 和 普通对象 在使用场景下有区别,在项目中使用数组的目的大多是为了遍历,更多的是使用数组的api
- 数组长度是多变的, index过大时会丢失响应性且性能代价太大
- 无法拦截数组原生方法如
push、pop、shift、splice等的调用,最终仍需 重写/增强
响应式原理
(a) vue2: Object.defineProperty
对象属性劫持,针对对象的属性
(b) vue3: Proxy
创建对象代理,拦截基本操作并自定义行为;针对整个对象
Proxy & Reflect
proxy:创建代理,用于对目标对象实现拦截/捕捉;一般针对场景:实现功能扩展/一些边界情况的处理;
Reflect:
- 为了在执行对应的拦截操作的方法时能传递正确的this上下文
- 确保完成原有的默认行为,然后在此基础部署额外的功能
列举一些常见的使用场景:
(1) 数据验证/参数校验(set拦截)
(2) 构造函数扩展
(3) 私有属性保护
(4) 多类容错检查
(5) proxy & reflect实现观察者模式
Proxy响应式
reactive函数:
通过proxy创建代理对象,根据目标数据类型选择不同的捕获器(handlers)处理,在这之前包含一些前置判断
(1) 对象类型(包含数组)捕获器:get/set/deleteProperty/has/OwnKeys
(2) 数组类型捕获器: 主要针对set/get捕获器
- set捕获器:针对
arr[1] = 2、arr.length = 0之类的操作,根据操作是否会改变length属性的值判断是否需要触发与length相关的副作用的执行 - get捕获器:针对
push、pop、shift、unshift、splice等操作,这些操作会读取和设置数组的length属性;根据具体情况对这些方法进行重写/增强
(3) 集合类型(map/set/WeakMap/WeakSet)捕获器:
track时机:get()、get size()、has()、forEach()trigger时机:add()、set()、delete()、clear()
ref函数: 参数不限制数据类型
- 原始值类型:不需要创建代理对象,只需对原始值进行一层包装,将原始值类型变成对象类型(
RefImpl实例对象, 通过.value的方式访问数据);实现自定义的get/set进行依赖收集和依赖更新 - 对象类型:不仅会创建RefImpl实例对象,还会调用reactive()函数转换为响应式数据
总结:
- 普通对象类型可以直接配合
Proxy提供的捕获器实现响应式 - 数组类型也可以直接复用大部分和普通对象类型的捕获器,但其对应的查找方法和隐式修改
length的方法仍然需要被重写/增强 - 为了支持集合类型的响应式,也对其对应的方法进行了 重写/增强
- 原始值数据类型主要通过
ref函数来进行响应式处理,在内部自定义get value(){}和set value(){}的方式实现响应式,核心还是将原始值类型转变为普通对象类型 ref函数可实现原始值类型转换为响应式数据,但ref接收的值类型并没只限定为原始值类型,若接收到的是引用类型,还是会将其通过reactive函数的方式转换为响应式数据
Proxy 与 Object.defineProperty的对比
proxy响应式优点:
- 性能较好
- 避免初始化时的属性遍历
- 嵌套属性只有访问某个属性时需要遍历下一级属性
- 可以监听属性的动态新增、删除,监听数组索引和length属性
参考链接: