Vue Listening Array Change Source Code Analysis
- 2021-08-03 08:34:50
- OfStack
In the code of the first article, ignoring the processing of the array, only concerned about the part that needs to be concerned, pretending that the array does not exist.
This article begins to consider the problem of arrays.
Start with the simplest
First consider a question, how to listen for changes in objects in an array. Ignore the array itself and its 1-value, and only consider the objects in the object array.
Traverse the array, and then call the observe method on each object in the array
// Upper 1 Unrewritten code that appears in the article, this 1 No repetition in the article
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
// If it is an array, all elements are traversed
if(Array.isArray(value)) {
this.observeArray(value);
} else {
this.walk(value);
}
};
Observer.prototype.observeArray = function observeArray(items) {
// Traverse all elements of the array, and perform a single element getter , setter Binding
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
Realistic requirements
Of course, the actual implementation will not be as simple as the above example. The official document describes the listening array as follows:
Vue contains a set of mutation methods for viewing arrays, so they will also trigger view updates. These methods are as follows:
push (), pop (), shift (), unshift (), splice (), sort (), reverse ()
Due to the limitations of JavaScript, Vue cannot detect the following changing arrays:
When you set the index of 1 item directly, for example: vm. items [indexOfItem] = newValue
When you modify the length of the array, for example: vm. items. length = newLength
Therefore, we should listen to 1 methods of the array itself.
A small function that is often used
def, recurring throughout the Vue source code, uses Object. defineProperty () to define the attribute key on obj (or possibly modify the existing attribute key):
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
// Turn into boole Value, if you don't pass parameters, change to false
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
Add 1 set of methods to an object
Add a set of methods to the object. If the environment supports proto, it is simple. It is good to directly point the proto of the object to this set of methods; If not, iterate through the 1 set of methods and add them to the object in turn as hidden attributes (i.e. enumerable: false, not found by the in keyword):
var hasProto = '__proto__' in {};
var augment = hasProto ? protoAugment : copyAugment;
function protoAugment(target, src) {
target.__proto__ = src;
}
function copyAugment(target, src, keys) {
for(var i = 0; i < keys.length; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
Let's start with a simple one
var arrayPush = {};
(function(method){
var original = Array.prototype[method];
arrayPush[method] = function() {
// this Pointing can be seen by the following test
console.log(this);
return original.apply(this, arguments)
};
})('push');
var testPush = [];
testPush.__proto__ = arrayPush;
// From the output, you can see that the above this It points to testPush
// []
testPush.push(1);
// [1]
testPush.push(2);
Pseudo-overwrite array prototype to listen for array changes
As stated in the official document, only seven methods need to be monitored: push (), pop (), shift (), unshift (), splice (), sort () and reverse (). These seven methods can be divided into two categories:
1. push (), unshift () and splice (), which may add new elements to the array;
2. The rest of the methods that do not add elements.
In order to avoid polluting the global Array, a new object based on Array. prototype is created, and then attributes are added to the object itself, and then the newly created object is added to value of Observer as a prototype or attribute to monitor its changes.
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
The next step is to iterate over the methods that need to trigger updates, attaching them in turn to the arrayMethods:
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function(method) {
// Gets the original array manipulation method
var original = arrayProto[method];
// In arrayMethods Create a new property on method And for method Specify a value (function)
// That is, rewriting arrayMethods Array method with the same name on
def(arrayMethods, method, function mutator() {
var arguments$1 = arguments;
var i = arguments.length;
var args = new Array(i);
// Pseudo-array arguments Convert to array form
// Why not [].slice.call(arguments) ?
while(i--) {
args[i] = arguments$1[i];
}
var result = original.apply(this, args);
// Cause arrayMethods Is to act as Observer In value The prototype of or directly as an attribute, so the this 1 Like is pointing Observer In value
// Of course, it needs to be modified Observer So that the value Have 1 Point Observer Its own attributes, __ob__ To link the two
var ob = this.__ob__;
// Store new array elements
var inserted;
// Consider several methods that may have new elements separately
switch(method) {
case 'push':
inserted = args;
break;
case 'unshift':
inserted = args;
break;
case 'splice':
// splice Methodology No. 1 3 A new element is added at the beginning of a parameter
inserted =args.slice(2);
break;
}
if(inserted) {
// For the newly added element getter , setter Binding
ob.observerArray(inserted);
}
// Trigger method
ob.dep.notify();
return result;
});
};
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
Update Observer
According to the comments in the above example code, rewrite Observer, so that the two are related to achieve the purpose of listening for array changes:
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
def(value, '__ob__', this);
// If it is an array, all elements are traversed
if(Array.isArray(value)) {
var argument = hasProto ? protoAugment : copyAugment;
argument(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};
References:
vue Early Source Learning Series 2: How to Listen for Changes in an Array