【vue2源码】vue2 改写数组的方法
vue2 响应式缺陷
vue2 的响应式由 Object.defineProperty 完成,这个方法是为对象设计的,数组肯定是无法使用。
- Object.defineProperty 的缺陷
Vue 2 使用 Object.defineProperty 实现响应式,但该 API:
- 无法监听数组索引变化(arr[0] = x)
- 无法检测 length 属性修改、
- 性能权衡
若要对数组索引实现响应式,需要:
- 遍历所有索引并用 defineProperty 包装
- 对大规模数组会造成严重性能问题
如何处理数组的方法
源码地址:vue\src\core\observer\index.ts
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
/*
value: 需要转为响应式的目标对象/数组
shallow: 是否浅层观察(不递归子属性)
mock: 是否为测试环境模拟
*/
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
// # 创建依赖收集器
this.dep = mock ? mockDep : new Dep()
// # 初始化 Vue 实例计数器
this.vmCount = 0
// # 给value添加__ob__属性指向当前Observer实例,__ob__: 标记该对象已被观察,避免重复处理
// # def(): Vue 的工具函数,等价于 Object.defineProperty
def(value, '__ob__', this)
if (isArray(value)) {
if (!mock) {
// # 原型链拦截(现代浏览器):修改 __proto__ 指向重写后的原型
if (hasProto) {
/* eslint-disable no-proto */
// # 修改数组原型链
;(value as any).__proto__ = arrayMethods
/* eslint-enable no-proto */
} else {
// # 方法拷贝(兼容旧环境):直接在数组上定义重写的方法
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
// # 降级方案:直接定义方法
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
// # 观察数组元素,调用 observe(),实现深层响应式
this.observeArray(value)
}
} else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// # 对象的响应式处理:递归实现
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
vue2 改写数组响应式的方法
源码路径:vue\src\core\observer\array.ts
// # 数组改写方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// # 缓存原始方法(如 Array.prototype.push)
const original = arrayProto[method]
// # 定义重写后的方法到 arrayMethods 对象上
def(arrayMethods, method, function mutator(...args) {
// ---- 步骤 1:执行原始方法 ----
const result = original.apply(this, args)
// ---- 步骤 2:获取关联的 Observer 实例 ----
const ob = this.__ob__
// ---- 步骤 3:处理新增元素(仅 push/unshift/splice 可能添加新元素) ----
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args // # push/unshift 的参数是新增元素
break
case 'splice':
inserted = args.slice(2) // # splice 的第三个参数起是新增元素
break
}
// # 如果存在新增元素,递归观察它们
if (inserted) ob.observeArray(inserted)
// notify change
// ---- 步骤 4:触发依赖更新 ----
if (__DEV__) {
// # 开发环境下传递额外调试信息
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
// # 生产环境直接通知更新
ob.dep.notify()
}
// # 返回原始方法的执行结果
return result
})
})
洗尽铅华始见金,褪去浮华归本真