JavaScript中Array.prototype上有很多常用的方法,MDN上也给出了介绍,虽然方法众多,但是可以改变数组内容的只有7个
(fill和copyWithin处于试验阶段,所以源码中没有适配),分别是:pop、push、shift、unshift、splice、sort、reverse。
这里的主要内容放在代码注释中,能够看得更清楚
Vue处理数组响应式的办法是劫持数组原型方法,将数组元素的原型替换为重写的数组原型方法
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // 列举出所有可以改变数组内容的方法名 const methods = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methods.forEach(function (method) { // 原来数组的方法 const original = arrayProto[method] // def工具,为对象设置函数属性 def(arrayMethods, method, function mutator (...args) { // 执行原方法,指定this const result = original.apply(this, args) // 每个属性都维护一个子的观察者,后面会说到 const ob = this.__ob__ let inserted // 处理新加入的参数 switch (method) { case 'push': case 'unshift': // push和unshift都会在这里处理,获取参数,参数也需要做响应式处理 inserted = args break case 'splice': // splice的新加的参数是从第三位开始的,如arr.splice(2, 1, 'bar') inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 通知更新,后面依赖收集会说到 ob.dep.notify() // 将原方法执行的结果返回 return result }) }) // util.js export function def(obj, key, value, enumerable = false) { Object.defineProperty(obj, key, { value, enumerable, writable: true, configurable: true }) } 复制代码
上面这段代码有点长,来总结一下它的主要功能
- 开始先使用
Object.create
创建一个新的对象,原型是Array.prototype
, - 然后列举能够改变数组的7个方法,遍历列举出来的数组方法,给创建出来的对象添加函数属性,期间执行原方法获取返回值,如果方法是可以传参的那就要对参数再进行观测,
- 然后通知视图更新,最后返回函数的返回值
现在具有响应式功能的数组方法已经重写完了,下一步就是将重写过的数组方法挂载到数组上了,这时候要将之前的Observer稍加改造,对要坚挺的数据进行类型检测,如果是数组元素就将重写的数组方法挂载到原型上
export default class Observer { constructor(value) { this.value = value // 用于维护依赖,后面会说 this.dep = new Dep() def(value, '__ob__', this) // 类型检测 if (Array.isArray(value)) { // 使用setPrototypeOf可以将第二个参数作为第一个参数__proto__ Object.setPrototypeOf(value, arrayMethods) this.observeArray(value) } else { // 之前有observe的过滤,能够到这里的不是数组就是对象 this.walk(value) } } walk(obj) { Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key])) } // 将数组元素遍历观察 observeArray(arr) { arr.forEach((i) => observe(i)) } } 复制代码
到这里数据观察过程已经基本了解了,但是这里并不是最终版,这里可以在数据发生变化时通过setter来更新视图,但是如果数据并没有被页面引用呢,即便是引用了,任何一个数据变化都会导致页面更新,代价太大了,这就需要我们后面要说的依赖收集了。