Question to consider
Let’s look at a piece of code and think about a few questions
<template>
<div>
<div v-for="item in testArr" :key='item'>
{{item}}
</div>
</div>
</template>
export defaut {
data() {
return {
testArr: [1.2.3]}},method: {
changeArr() {
this.testArr = [1.2.3.4]},changeLength() {
this.testArr.length = 2
},
changeArrItemByIndex() {
this.testArr[0] = 100;
},
addArrItem() {
this.testArr.push(4); }}}Copy the code
- When will
testArr
Array direct assignment, executechangeArr
Methods; - When changing the length property of the data, execute
changeLength
Methods; - Executes when changing an element of data by array subscript
changeArrItemByIndex
Methods; - With the push method, execute
addArrItem
Methods;
In the above four cases, think about whether the page data will be updated?
Array responsivity principle analysis
Vue uses object.defineProperty to add get and set methods to the data, then does dependency collection when calling get and notifying watcher objects to update the data when the set changes. Arrays have a different situation, we look at the source.
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
Object.defineProperty
def(value, '__ob__'.this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
Copy the code
As you can see from the code in the Observer class, it does something special when the data it is listening for is an array. Now let’s parse this code.
hasProto
export const hasProto = '__proto__' in {}
Copy the code
Check whether the object in the current browser has a __proto__ attribute for browser compatibility.
protoAugment
The protoAugment method is called if the current browser supports the __proto__ attribute, so let’s look at the code
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
Copy the code
This method also has very little code, and essentially sets the prototype we passed in to the array to the arrayMethods we passed in.
Let’s look at the code for arrayMethods
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from '.. /util/index' 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) 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 })})Copy the code
You can see that with Object.create(arrayProto) you create an Object that is a prototype of the original array, and then you store a bunch of array methods in the methodsToPatch array, and they all have one thing in common: they all change the original array. The purpose of this is to make it easier to add listeners to modify the array methods when they are executed.
ObserveArray method code
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
Copy the code
Def method code
The def method sets the key of the object.
export function def (obj: Object, key: string, val: any, enumerable? : boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable:!!!!! enumerable,writable: true.configurable: true})}Copy the code
Add a listener for each item of data.
copyAugment
The copyAugment method is always the same as the protoAugment method, adding listeners to the array but dealing with browser compatibility issues.
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])
}
}
Copy the code
conclusion
The reactive data of an array in VUE is obtained by modifying the original array method and then listening for each item in the array.
Vue does not handle 2,3, and is only responsive when it calls several methods that change the array. In case 1, the direct assignment is handled by vue’s reactive implementation, so only 2,3 does not update the view.