Vue Responsive principle - how to monitor changes in the Array?

Memories

In the previous Vue responsive principle - understood Observer, Dep, Watcher simply explain Observer, Dep, Watcherthe relationship between the three.

In Observerpseudo-code, we simulate the following code:

class Observer {
    constructor() {
        // 响应式绑定数据通过方法
    	observe(this.data);
    }
}

export function observe (data) {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
       // 将data中我们定义的每个属性进行响应式绑定
       defineReactive(obj, keys[i]);
    }
}

export function defineReactive () {
    // ...省略 Object.defineProperty get-set
}
复制代码

Today we'll learn more Observerthere is also done something.

How to change Array listening?

dataIf the data is how to do an array? We found that Object.definePropertythe array is responsive technology is flawed.

Although we can listen to the change of the index.

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log('我被读了,我要不要做点什么好?');
            return val;
        },
        set: newVal => {
            if (val === newVal) {
                return;
            }
            val = newVal;
            console.log("数据被改变了,我要渲染到页面上去!");
        }
    })
}

let data = [1];

// 对数组key进行监听
defineReactive(data, 0, 1);
console.log(data[0]); // 我被读了,我要不要做点什么好?
data[0] = 2; // 数据被改变了,我要渲染到页面上去!
复制代码

But definePropertycan not detect a change in the length of the array, that is accurate by changing the length and the increased length can not be monitored. This situation can not trigger any change.

data.length = 0; // 控制台没有任何输出
复制代码

And the cost of any indices of listening is also relatively high, consolidation of a number of other factors, Vue used another program to deal with.

First, we observeneed to transform the look, add a separate deal with an array.

// 将data中我们定义的每个属性进行响应式绑定
export function observe (data) {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
        // 如果是数组
        if (Array.isArray(keys[i])) {
            observeArray(keys[i]);
        } else {
            // 如果是对象
            defineReactive(obj, keys[i]);
        }
    }
}

// 数组的处理
export function observeArray () {
    // ...省略
}
复制代码

Then the next we should consider Arraychanges in how to listen?

Vue Very simple and crude solution to this problem array, the array is to be able to change the method to do some hands and feet.

We know that there are many ways to change the array, for example such as pushthe method it. pushThere is Array.prototype, if we can on

Able to intercept the prototype pushmethod is not something you can do it?

Object.defineProperty

Objects in the attribute descriptor existing in two main forms: data descriptor and access descriptor . Access descriptor is getter-setter function described by the attribute, that is, we used to make responsive target binding. Object.defineProperty-MDN

While we can not use Object.definePropertythe array responsive handling, that is getter-setter, but there are other functions can be for our use. Is data descriptor , data descriptor is an attribute having a value, the value may be written to, may not be written.

value

Corresponding to the attribute value. JavaScript can be any valid value (numeric, objects, functions, etc.). The default is undefined .

writable

If and only if the property writableis truetime, valuein order to be assignment operator change. The default is false .

So we just put the method on a prototype, were valuere-assigned.

The following code in the process of re-evaluation, we can get to all method names and parameters.

function def (obj, key) {
    Object.defineProperty(obj, key, {
        writable: true,
        enumerable: true,
        configurable: true,
        value: function(...args) {
            console.log('key', key);
            console.log('args', args); 
        }
    });
}

// 重写的数组方法
let obj = {
    push() {}
}

// 数组方法的绑定
def(obj, 'push');

obj.push([1, 2], 7, 'hello!');
// 控制台输出 key push
// 控制台输出 args [Array(2), 7, "hello!"]
复制代码

With the above code that we can know that the user uses the stereotype of the array of methods and parameters we can intercept, the intercept of the notification process can do some changes.

Vue monitor Array trilogy

Next, take a look at Vueis how to achieve it -

The first step: first get the native Arrayprototype method, because the method still need a native to help us achieve changes in the array after interception.

Step Two: The Arrayprototype method is used Object.definePropertyto do some interception.

The third step: the need intercepted Arraytype of data transformation prototype after prototype point.

We will carry out reform under the code, or in the process of intercepting the parameters you want passed to the method developer's native ensure that the array is changed in accordance with the developer's idea, then we'll do an updated view of such an operation.

const arrayProto = Array.prototype // 获取Array的原型

function def (obj, key) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        value: function(...args) {
            console.log(key); // 控制台输出 push
            console.log(args); // 控制台输出 [Array(2), 7, "hello!"]
            
            // 获取原生的方法
            let original = arrayProto[key];
            // 将开发者的参数传给原生的方法,保证数组按照开发者的想法被改变
            const result = original.apply(this, args);

            // do something 比如通知Vue视图进行更新
            console.log('我的数据被改变了,视图该更新啦');
            this.text = 'hello Vue';
            return result;
        }
    });
}

// 新的原型
let obj = {
    push() {}
}

// 重写赋值
def(obj, 'push');

let arr = [0];

// 原型的指向重写
arr.__proto__ = obj;

// 执行push
arr.push([1, 2], 7, 'hello!');
console.log(arr);
复制代码

After being changed arr.

Vue source parsing

array.js

VueIn array.jsthe rewrite of methodsToPatchthe seven methods and prototype rewritten exposed to.

// Object.defineProperty的封装
import { def } from '../util/index' 

// 获得原型上的方法
const arrayProto = Array.prototype 

// Vue拦截的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

// 将上面的方法重写
methodsToPatch.forEach(function (method) {
    def(arrayMethods, method, function mutator (...args) {
        console.log('method', method); // 获取方法
        console.log('args', args); // 获取参数

    	// ...功能如上述,监听到某个方法执行后,做一些对应的操作
      	// 1、将开发者的参数传给原生的方法,保证数组按照开发者的想法被改变
        // 2、视图更新等
    })
})

export const arrayMethods = Object.create(arrayProto);
复制代码

observer

During the data observertime-bound, we first determine whether hasProto, if present __proto__, directly to valuethe __proto__point prototype after the rewrite. If you can not use __proto__, it looks like some browser vendors did not materialize. Then direct circulation arrayMethodsto the body of these methods it is directly attached to the valuebody as well. After all invoke a method is to go to find itself, when it can not find its own methods of closing, only to find the prototype.

// 判断是否有__proto__,因为部分浏览器是没有__proto__
const hasProto = '__proto__' in {}
// 重写后的原型
import { arrayMethods } from './array'
// 方法名
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);

// 数组的处理
export function observeArray (value) {
    // 如果有__proto__,直接覆盖                
    if (hasProto) {
        protoAugment(value, arrayMethods);
    } else {
        // 没有__proto__就把方法加到属性自身上
        copyAugment(value, arrayMethods, )
    }
}

// 原型的赋值
function protoAugment (target, src) {
    target.__proto__ = src;
}

// 复制
function copyAugment (target, src, keys) {
    for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i]
        def(target, key, src[key]);
    }
}
复制代码

By the above code, we found that there is no direct modification Array.prototype, but directly arrayMenthodsassigned to valuethe __proto__. Because it does not pollute the global Array, arrayMenthodsonly datathe Arrayeffect.

to sum up

Because the cost of an array of listening and brought some problems, Vueuse the program instead of rewriting the prototype. Some methods to intercept the array, in this process, and then do notice changes and other operations.

Some code in this article are Vuethe source simplified in order to facilitate understanding. Understand the thinking, it is easy to understand the source code.

The series is written ~

Github blog please share ~

Reproduced in: https: //juejin.im/post/5cf606d6f265da1b8e708ba6

Guess you like

Origin blog.csdn.net/weixin_34268579/article/details/91481114