在Vue2中如果直接通过下标修改数组的值,不会触发视图的更新,只能通过push、pop、shift、unshift、splice、sort和reverse7种方法改变才会触发响应式更新。尤大的解释是直接监听数组的下标会带来性能损耗,所以通过hack的方式覆写这些编译方法。
其实现原理如下,每行代码都进行了注释:
// 源码目录:src/core/array.js
// 获取工具方法def,定义对象的数据描述符
import {
def } from '../util/index'
// 获得Array的原型对象
const arrayProto = Array.prototype
// 继承Array的原型,后面defineProperty修改属性描述符,实现原型法的hack
export const arrayMethods = Object.create(arrayProto)
// hack的7种会修改数组元素的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 通过修改数据描述符,拦截这7种方法,并派发更新
methodsToPatch.forEach(function (method) {
// 暂存初始Array的原型方法
const original = arrayProto[method]
// 修改数据描述符
def(arrayMethods, method, function mutator (...args) {
// 调用原生方法,存储结果
const result = original.apply(this, args)
// 获取当前Vue实例的Observer
const ob = this.__ob__
// 保存插入的数据,需要转换成响应式数据
let inserted
// push、unshift、splice三种方法会插入,分别取得其对应的插入值
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 将插入的数据也转换成响应式
if (inserted) ob.observeArray(inserted)
// 派发更新
// 每个Observer都管理一个依赖收集器Dep,当触发setter时,就是通过Dep派发更新,让Watcher更新视图。
ob.dep.notify()
// 返回执行结果
return result
})
})
其中__ob__获取Observer实例,我们查看observer的源码
// 源码目录:src/observer/index.js
// 观察数组
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// 使数组元素变为响应式数据
observe(items[i])
}
}