vue响应式

191 阅读4分钟

数据响应式

  • 侵入式: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] = 2arr.length = 0之类的操作,根据操作是否会改变length属性的值判断是否需要触发与length相关的副作用的执行
  • get捕获器:针对pushpopshiftunshiftsplice等操作,这些操作会读取和设置数组的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的对比

github.com/Rashomon511…

proxy响应式优点:

  • 性能较好
  • 避免初始化时的属性遍历
  • 嵌套属性只有访问某个属性时需要遍历下一级属性
  • 可以监听属性的动态新增、删除,监听数组索引和length属性

参考链接:

vue3响应式