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:getterThe operation andsetterOperation.
  • What operations do methods that access arrays need to intercept in the proxy? The answer isgetterBecause a method is also a property of an object, when an object property is acquired, it is triggeredgetterOperation.

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:

  • pushThe operation refers to the context of the current object, i.ethisOr 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

  1. The first thing you need to know is the next index, so you need to getproxyArray.length(visitedgetter)
  2. Set the next subscriptproxyArray[proxyArray.length] = 4
  3. proxyArray.lengthSince 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 arraylengthProperty 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 notsetterMethod, but can it be filtered out? Of course not, if the user overwrites the proxy dataforEachMethod, then the render function is triggered to re-execute the new oneforEachMethods.
  • length: When the user performs multiple operations on an array, for examplepush,popAnd so onlengthProperty, which triggers the rendering function to re-render.
  • Modify theindex: Needless to say, this is alsoVue3.0Support 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,lastIndexOfThree methods will not be listened to.