preface
Before you study this article, you need to know:
Proxy
Reflect
1. What are the native methods of ES6 arrays?
The native methods for operating on ES6 arrays are as follows:
Vue3.0 supports more native methods for observing arrays than Vue2.0, and does not need to write specific methods to support it. Observations of array native methods can be incorporated into the response code for array subscripts and object attributes. How does this work? This article will reveal it to you!
2. Array proxy object some interesting phenomena
First, let’s be clear about two points:
- Intercepting an operation on an array intercepts two operations:
getter
The operation andsetter
Operation. - What operations do methods that access arrays need to intercept in the proxy? The answer is
getter
Because a method is also a property of an object, when an object property is acquired, it is triggeredgetter
Operation.
Let me write a very simple handler to proxy an array.
let handler = {
get(target, key, receiver) {
console.log('get operation', key);
return Reflect.get(target, key);
},
set(target, key, value, receiver) {
console.log('set operation', key, value);
return Reflect.set(target, key, value); }};Copy the code
We then create an array of proxy objects and call push:
let proxyArray = new Proxy([1.2.3], handler);
proxyArray.push(4);
// get operation push
// get operation length
// set operation 3 4
// set length 4
Copy the code
The following points can be seen from the above:
push
The operation refers to the context of the current object, i.ethis
Or a proxy objectproxyArray
, rather than referring to the context of the native object.- The proxied object is contextual and different from the native object, so it is also an instance type, but the content is proxied from the native object.
The reason for the above print is simple and explained below:
When push is called, the push method performs the following steps
- The first thing you need to know is the next index, so you need to get
proxyArray.length
(visitedgetter
) - Set the next subscript
proxyArray[proxyArray.length] = 4
proxyArray.length
Since the increase
This is what happens in the **push** code. The same thing happens when you use a push method using a native array. Push code is native code, but it can be derived.
3. Use an example to illustrate how Vue3.0 implements data binding of array native objects (emphasis)
Again, we need to set up a specific scenario: how does the whole process of adding dependencies and responding to data look in a rendering function using forEach?
Let’s get rid of the source code and think about the following question
1) Specify the purpose of using forEach
ForEach is typically used to iterate through an array and display all the values of the array. The timing of the trigger is as follows:
- Write to the existing contents of the array, modify the displayed contents, need to re-render
- Expand or shrink an array, that is, modify the array
length
Property that needs to be rendered
2) Dependency add and trigger conditions
Let’s execute the following forEach method with the proxy array above to see the result:
proxyArray.forEach(item= > item);
// get forEach
// get operation length
// get operation 0
// get operation 1
// get operation 2
Copy the code
You can see that there are three dependency attributes added when forEach is used:
forEach
: Usually notsetter
Method, but can it be filtered out? Of course not, if the user overwrites the proxy dataforEach
Method, then the render function is triggered to re-execute the new oneforEach
Methods.length
: When the user performs multiple operations on an array, for examplepush
,pop
And so onlength
Property, which triggers the rendering function to re-render.- Modify the
index
: Needless to say, this is alsoVue3.0
Support the implementation of response data after modification of subscripts.
3) Framework source code test code to test ideas
To verify our idea, we wrote the test code in the Reactivity directory (below), which passed the test:
it('Test Array forEach func'.function() {
const rawArray = [1.2.3];
// @ts-ignore
const proxyArray = reactive(rawArray);
resumeTracking();
const runner = effect((a)= > {
proxyArray.forEach(item= > item);
});
const isUndef = (tar) = > {
return typeof tar === 'undefined' || tar === null;
}
const isDef = (tar) = >! isUndef(tar); expect(targetMap.get(rawArray).size ===5).toBeTruthy();
expect(isDef(targetMap.get(rawArray).get('forEach'))).toBeTruthy();
expect(isDef(targetMap.get(rawArray).get('length'))).toBeTruthy();
expect(isDef(targetMap.get(rawArray).get('0'))).toBeTruthy();
expect(isDef(targetMap.get(rawArray).get('1'))).toBeTruthy();
expect(isDef(targetMap.get(rawArray).get('2'))).toBeTruthy();
expect(targetMap.get(rawArray).get('forEach').has(runner)).toBeTruthy();
expect(targetMap.get(rawArray).get('length').has(runner)).toBeTruthy();
expect(targetMap.get(rawArray).get('0').has(runner)).toBeTruthy();
expect(targetMap.get(rawArray).get('1').has(runner)).toBeTruthy();
expect(targetMap.get(rawArray).get('2').has(runner)).toBeTruthy();
})
Copy the code
So it proves that our idea is correct.
4) If you add a new element to an array, how do you add a dependency to it?
A: Every time effect is executed, it uses getter methods (to get the data), and the framework removes effection-related dependencies before each execution and adds them again at execution time.
4. Includes, indexOf, and lastIndexOf special examples
In the source code, there is this code:
const arrayIdentityInstrumentations: Record<string.Function> = {};
['includes'.'indexOf'.'lastIndexOf'].forEach(key= > {
arrayIdentityInstrumentations[key] = function(value: unknown, ... args:any[]
) :any {
// Get the native mode of the object and execute the native method
return toRaw(this)[key](toRaw(value), ... args) } });Copy the code
What are the effects of taking these methods out in the first place?
To answer this question, we would like to turn our attention to the conclusion of the push example in title 2 (interesting phenomenon of Array proxies), where the proxy object’s context (this) is accessed in its method of executing an array. The push method will trigger proxy interception to add dependencies if it accesses other properties of the proxy object.
Instead, we return the result of the execution of the native object. We do not want to trigger the above three methods to access the array properties. (The proxy does not do anything with the native object, so there is no dependency added to return the result of the execution of the native object.)
Add getter interceptor methods and it becomes obvious what the framework source code is meant to be.
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
// This operation is to reduce unnecessary dependency additions. When accessing includes, it intercepts the subscripts of includes, length, and 0 -> target
// We move to native objects to avoid adding dependencies
if (isArray(target) && hasOwn(arrayIdentityInstrumentations, key)) {
return Reflect.get(arrayIdentityInstrumentations, key, receiver)
}
// If the method is in an array, it will not be executed here, so there is no dependency on the method, so the author clearly does not want to listen for these methods
// code...
track(target, TrackOpTypes.GET, key) // trace to add dependencies
// code...}}Copy the code
5. Summary
- ES6’s interceptor layer can automatically handle array methods depending on the method.
indexOf
,includes
,lastIndexOf
Three methods will not be listened to.