回答这个问题,实际上回答的是:Vue 的数据响应式原理有什么限制?理解了 Vue 的数据响应式原理的限制,就能够很好回答这个问题。数组
在实际使用 Vue 的过程当中,对于响应式 Data 的声明,必须将须要响应的各个属性都逐一在声明阶段写清楚。若是对于一个响应式的 Object,动态增长了某一个属性,那么 Vue 是没法为该新增属性作响应式处理的。举个栗子:bash
export default {
data(){
return {
foo : 1,
bar : {tau : "test"}
}
}
//给data动态增长属性newFoo, 页面没法响应式
this.data.newFoo = "123"
//给bar动态增长属性tau2, 页面没法响应
this.data.bar.tau2 = "tell me"
复制代码
理解了这个限制,咱们再来看看数组的状况。咱们会常常对数组进行的操做是怎样的呢?app
var array1 = ['t1','t2','t3']
//动态修改元素
array1[0] = 'tt1'
//动态增长元素
array1[4] = 't5'
//push方法增长数组元素
array1.push("1234")
//动态改变元素的长度
array1.length = 100
复制代码
这些常规操做,若是只是经过常规的拦截 key、value 的方式来进行数据响应式,那么明显没法彻底覆盖。而且成本过高,设想一下:若是用户直接写一个array1[1000] = 'haha'
,Vue 是否要为其 1000 个子元素(极有多是都为 null )都响应化处理呢?post
另外,值得注意的是,之因此对象能够在 data 声明的时候写死,是由于页面操做中的对象属性基本上能够在编写的时候肯定。但页面中数组的变化就不同,其变化基本上没法预测。而 JS 语言中的数组对其子元素的属性不限制,因而更加凌乱。ui
那么,Vue 是采用了什么方式来进行数组的数据响应化实现呢?this
咱们来接着上篇文章的内容,看源码。spa
查看/src/observer/array.js
,能够知道Vue对数组的七个方法进行了拦截处理:prototype
const arrayProto = Array.prototype
//先克隆一份数组原型
export const arrayMethods = Object.create(arrayProto)
//七个变异方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
//执行原始方法
const result = original.apply(this, args)
//额外通知变动,固然,只有这7个方法才会有这个待遇
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
//此处通知,能够知道数组更新行为
ob.dep.notify()
return result
})
})
复制代码
咱们来看,在定义 Observer 的时候,如何处理数组:code
import { arrayMethods } from './array'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
//值得留意的是:Observer对象在一个Vue实例中是存在多个的,取决于data数据中嵌套了几个Object对象或数组对象
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
//若是是数组
if (Array.isArray(value)) {
//若是可以使用原型特性,直接将变异方法赋予响应化数组的原型链上
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
//若是没法使用原型,那么经过defineProperty的方式将变异方法赋予响应化数组
copyAugment(value, arrayMethods, arrayKeys)
}
//接着,对数组子元素,进行新一轮的observe数据响应化的过程
this.observeArray(value)
} else {
//若是是对象
this.walk(value)
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
function protoAugment (target, src: Object) {
target.__proto__ = src
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
复制代码
这个过程并不难看懂。至此,咱们能够回答这个问题:server
data: {
obj: {foo: 'foo'}
bar: ['tua', 'tea']
}
//下面的这个操做,是否会触发数据响应化过程呢?
this.bar[0] = 'testNew';
复制代码