你在面试中,有没有被问到vue是如何对数组做响应式处理的呢?
1、找到响应式处理入口
进入页面,Vue开始初始化,执行new Vue(),进入到Vue的构造函数中,在构造函数中执行了
_init()
方法。
在
_init()
这个方法中,调用了initState()方法 => 在initState()方法中,调用了initData()方法 => 在initData()方法中,调用了observe()方法。
observe()方法就是Vue进行响应式处理的入口。
2、找到数组响应式处理入口
在observe()方法中,创建了Observer实例,在创建Observer实例的过程中,对传入的数据进行了判断,如果是数组,单独对数组进行处理,如果不是数组,调用walk方法对数据进行响应式处理。本文只关心对数组的处理,源码如下(已精简)
constructor (value: any) {
// 如果是数组,对数组进行特殊处理
if (Array.isArray(value)) {
// 判断浏览器是否支持原型 __proto__ 下面两个方法实质处理方式是一样的
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历数组的每一项,调用observe方法对数组的每一项以递归的方式进行响应式处理
this.observeArray(value)
} else {
this.walk(value)
}
}
3、对数组进行处理
这里假设浏览器支持
__proto__
原型,进入到protoAugment方法中,这个方法很简单,
target.__proto__ = src
就是将数组的原型指向arrayMethods。我们主要关心的就是arrayMethods,这也是对数组处理的核心部分。其实这一部分也很简单,直接贴上源码(已精简):
const arrayProto = Array.prototype
// arrayMethods 的原型就是数组的原型
export const arrayMethods = Object.create(arrayProto)
// 数组中会修改自身的7个方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// 通过def方法将数组中会修改自身的方法进行重写
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)
// notify change 数组改变后调用 notify方法 通知 Watcher 去更新视图
ob.dep.notify()
return result
})
})
从源码上很容易可以看出,vue对数组的响应式处理,其实质还是调用了数组原型上的方法,在数组发生变化后调用了notify去派发更新。
4、几个需要注意的点
1> ES6新增的fill方法、copyWithin方法也会改变数组自身,不会触发视图更新
2> 使用数组索引的方式修改数据,不会触发视图更新
3> 修改数组的length属性,不会触发视图更新
5、延伸
这里提到了数组发生变化后,调用了notify方法通知Watcher更新视图。那么面试官可能会追问你,什么时候进行的依赖收集呢?你可以看一下这篇文章:vue依赖收集的具体时机