A problem with Vue when operating on arrays
In Vue, we have some problems manipulating arrays. Why is the page not refreshed even though the data has been modified? The answer to this question has been given in the official documentation, the precautions for detecting change
Due to JavaScript limitations, Vue cannot detect array and object changes. However, there are ways to circumvent these limitations and make them responsive.
Vue cannot detect changes to the following arrays:
- When you set an array item directly using an index, for example:
vm.items[indexOfItem] = newValue
- When you modify the length of an array, for example:
vm.items.length = newLength
7 ways of packaging
Vue wraps seven methods of Array that operate on the Array to trigger a page refresh.
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Use of 7 methods
-
The push() method is used to add one or more elements to the end of an array, returning array length
-
The pop() method removes the last element of the array and returns the deleted element
-
The shift() method deletes the first element of an array and returns the deleted element
-
The unshift() method is used to add one or more elements to the head of an array, returning array length
-
The splice() method is used to delete, add, and replace elements,
-
arrayObject.splice(index,howmany,item1,….. ItemX) the first argument index is the subscript, the second argument howmany is the number of deleted, and the third and subsequent arguments are elements added like arrays
-
If only the first element is passed, all elements after index are deleted
-
If two arguments are passed, howmany elements after the start index are removed
-
If you pass three parameters, remove howmany elements after the start index index and add items
-
-
The sort() method is used to sort arrays by character encoding if no arguments are passed, or by passing a callback function with two parameters and sorting by the return value.
- If a is less than b, which should precede B in the sorted array, returns a value less than 0.
- If a is equal to b, 0 is returned.
- If a is greater than b, return a value greater than 0.
-
The reverse() method is used to reverse the order of elements in an array
The implementation principle of 7 array methods
These 7 methods can trigger the page refresh not because they can be triggered in the first place, but because the 7 methods are wrapped in special logic in Vue
.\src\core\observer\array.js
import { def } from '.. /util/index'
// Store the Array prototype object
const arrayProto = Array.prototype
// Create a prototype for an arrayMethods object pointing to an arrayProto object
export const arrayMethods = Object.create(arrayProto)
// Define an array of 7 method names
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
// Iterate through an array of 7 method names
methodsToPatch.forEach(function (method) {
// Store the original prototype method of Array. It is called here because you only need to fetch it once, using closures to trade space for time
const original = arrayProto[method]
// def is a simple wrapper around defineProperty, adding our wrapped methods to the arrayMethods object
def(arrayMethods, method, function mutator (. args) {
// Call the original Array prototype method, do the original logic of the method, and then process the added logic
const result = original.apply(this, args)
// __ob__ is the Dep object
const ob = this.__ob__
// The element added to the array needs to be processed responsively, and the element that needs to be processed is stored here
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// Respond to the added element
if (inserted) ob.observeArray(inserted)
// Notify Watcher to update the view
ob.dep.notify()
// Return the value of the original method
return result
})
})
Copy the code
As you can see, the arrayMethods object with the prototype array. prototype is exposed. In this object, we wrap the 7 methods in Array.
So, once we wrap it, how does it modify our array object in data, because it changes its methods when it responds to the array object, and it changes them in two ways,
- In our environment objects have
__ob__
Property directly points the array’s prototype to oursarrayMethods
Object, that is, add a layer to the prototype chain, which is our arrayMethods, when the array object calls these seven methods, and it can’t find them in the object, it will look for them in the prototype object, and it will call them directly - If there is no
__ob__
Object, we define these seven methods directly into our array object
.\src\core\observer\index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__'.this)
if (Array.isArray(value)) {
if (hasProto) {
// Change the prototype chain if there is an __ob__ object
protoAugment(value, arrayMethods)
} else {
Otherwise, define methods directly in array objects
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {... } observeArray (items:Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
// Modify the prototype chain
function protoAugment (target, src: Object) {
target.__proto__ = src
}
// Define seven methods into an array object
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
You can see our arrayMethods added to the Books prototype chain
Use vue.set to manipulate arrays
Using the set method to manipulate an array is essentially a call to the splice method.
.\src\core\observer\index.js
export function set (target: Array<any> | Object, key: any, val: any) :any {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
}
Copy the code
Array operations in Vue3
The responsivity principle of Vue3 is to use proxy, which is different from defineProperty used in Vue2 and does not have the above problems. You can modify the array by subscripting or directly modify the array length