第8章 数组响应化处理深度解析

8.1 数组响应化的特殊挑战

8.1.1 数组操作类型分析

操作类型示例方法响应化难度
变异方法push/pop/shift/unshift可拦截
替换方法splice/sort/reverse可拦截
索引操作arr[0] = newVal无法检测
长度修改arr.length = 0无法检测

8.1.2 设计约束

  1. 兼容性:需要支持ES5环境
  2. 性能:不能对大型数组造成明显负担
  3. 透明性:保持数组原生方法的行为不变

8.2 数组方法拦截实现

8.2.1 原型链重写方案

const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto) // 创建纯净副本

const methodsToPatch = [
  'push', 'pop', 'shift', 'unshift',
  'splice', 'sort', 'reverse'
]

methodsToPatch.forEach(method => {
  const original = arrayProto[method]
  
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    
    // 处理新增元素
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    
    // 响应化新增元素
    if (inserted) ob.observeArray(inserted)
    
    // 通知变更
    ob.dep.notify()
    return result
  })
})

关键实现解析

  1. 原型链继承arrayMethods继承原生数组方法
  2. 方法重写:在调用原生方法后触发通知
  3. 新增元素处理:特别处理可能添加新元素的方法

8.3 数组的响应化初始化

8.3.1 Observer类增强

class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep()
    
    def(value, '__ob__', this) // 标记已观察
    
    if (Array.isArray(value)) {
      // 数组的特殊处理
      this.observeArray(value)
      protoAugment(value, arrayMethods) // 修改原型链
    } else {
      this.walk(value)
    }
  }

  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i]) // 递归观察数组元素
    }
  }
}

function protoAugment(target, src) {
  target.__proto__ = src // 直接修改原型链
}

原型链修改示意图

观测数组实例的原型链:
arr → Vue包装的原型对象 → 原生Array.prototype

8.4 Vue.set/Vue.delete 实现

8.4.1 Vue.set 源码解析

function set(target, key, val) {
  // 处理数组情况
  if (Array.isArray(target)) {
    key = Math.max(key, 0)
    target.splice(key, 1, val) // 利用splice触发通知
    return val
  }
  
  // 处理对象情况
  if (key in target) {
    target[key] = val
    return val
  }
  
  const ob = target.__ob__
  if (!ob) {
    target[key] = val
    return val
  }
  
  defineReactive(ob.value, key, val) // 响应化新属性
  ob.dep.notify()
  return val
}

8.4.2 Vue.delete 源码解析

function del(target, key) {
  if (Array.isArray(target)) {
    target.splice(key, 1) // 数组使用splice
    return
  }
  
  if (!hasOwn(target, key)) return
  delete target[key]
  
  const ob = target.__ob__
  if (!ob) return
  ob.dep.notify()
}

统一处理策略

  1. 数组使用splice方法触发更新
  2. 对象直接删除属性并通知
  3. 确保删除操作触发视图更新

8.5 数组响应化的局限与解决方案

8.5.1 无法检测的情况

// 情况1:直接索引设置
vm.items[0] = newValue // 不会触发视图更新

// 情况2:修改数组长度
vm.items.length = 0 // 不会触发视图更新

8.5.2 解决方案比较

方法示例原理
Vue.set()Vue.set(arr, 0, newVal)调用splice
Array.prototype.splicearr.splice(0, 1, newVal)触发重写的方法
整体替换arr = arr.map(…)引用变更触发响应

8.6 性能优化策略

8.6.1 大型数组处理技巧

// 冻结大型数组(避免响应式开销)
const largeArray = Object.freeze([/* 大量数据 */])

// 分块处理
function chunkUpdate(arr, newData) {
  arr.splice(0) // 清空原数组
  newData.forEach((item, i) => {
    if (i % 100 === 0) {
      // 分批次触发更新
      Vue.nextTick(() => arr.push(...newData.slice(i, i+100)))
    }
  })
}

8.6.2 响应式标记机制

function observe(value) {
  if (!isObject(value)) return
  
  // 跳过被标记为非响应的对象
  if (value.__ob__ && value.__ob__.skip) return
  
  // 正常响应化处理...
}

本章重点总结:

  1. 方法拦截:通过原型链重写实现数组方法监听
  2. 边界处理:Vue.set/Vue.delete 的补充机制
  3. 性能权衡:数组响应化的设计取舍
  4. 最佳实践:安全操作数组的正确方式

深度实践建议

  1. 对比直接修改索引和使用Vue.set的区别
  2. 实现自定义数组方法拦截器
  3. 性能测试大型数组的不同操作方式
// 性能测试示例
const vm = new Vue({
  data: { items: Array(10000).fill(0) }
})

// 测试不同修改方式
console.time('直接赋值')
vm.items[5000] = 1 // 不会触发更新
console.timeEnd('直接赋值')

console.time('Vue.set方式')
Vue.set(vm.items, 5000, 1) // 触发更新
console.timeEnd('Vue.set方式')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道不尽世间的沧桑

作者想喝瓶哇哈哈,谢谢大佬

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值