Responsive principle of Vue source code learning

foreword

 Responsive means that when the data changes, Vue will notify the code that uses the data. When the data changes, the view will also be notified to change. Next, we will simply implement the core code part of the responsive principle in Vue.

Source code analysis

Observer

 The purpose of the Observer class is to convert a normal object into an object whose attributes at each level are responsive (can be detected).
Implementation part:

export default class Observer {
    
    
    constructor(value) {
    
    
        // 每一个Observer的实例身上,都有一个dep
        this.dep = new Dep();
        // 给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)添加了__ob__属性,值是这次new的实例
        // 对__ob__每个属性使其无法进行枚举遍历,def这里是给予这个value添加__ob__并且无法进行枚举,
        // 并且其他属性也无法被修改  
        // 这个地方使得当前元素的__ob__是这个Observer类
        def(value, '__ob__', this, false);
        // console.log('我是Observer构造器', value);
        // 不要忘记初心,Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object
        // 检查它是数组还是对象
        if (Array.isArray(value)) {
    
    
            // 如果是数组,要非常强行的蛮干:将这个数组的原型,指向arrayMethods
            // 修改其对应的原型为arrayMethods
            Object.setPrototypeOf(value, arrayMethods);
            // 让这个数组变的observe
            this.observeArray(value);
        } else {
    
    
            // 如果是对象的话则挨个的去进行遍历对象的每个属性
            this.walk(value);
        }
    }

    // 遍历
    walk(value) {
    
    
        // 对每个对象进行设置响应式
        for (let k in value) {
    
    
            defineReactive(value, k);
        }
    }

    // 数组的特殊遍历
    observeArray(arr) {
    
    
        for (let i = 0, l = arr.length; i < l; i++) {
    
    
            // 逐项进行observe
            observe(arr[i]);
        }
    }
    
};

const def = function (obj, key, value, enumerable) {
    
    
    Object.defineProperty(obj, key, {
    
    
        value,
        enumerable,
        writable: true,
        configurable: true
    });
};

 1. First, set an attribute of the object passed in to Observer as __ob__, which means that when we usually print out the variable object in Vue, we can see that there is an __ob__ attribute in the object, then this attribute is actually Observer instances. In the code, we use the def function to define the __ob__ attribute, give it hijacking and enumerable is false, and cannot be enumerated.

2. Determine whether the object passed in is an array or an object:
 if it is an array, call observeArray to traverse each item of the array in order to observe (this method will be introduced below, in fact, add an Observer instance to each item of the array ), for the array, we also need to rewrite the prototype, rewrite the seven methods of the array object, so as to monitor the data inserted in the insert method, and perform data monitoring on the inserted data, and change the prototype of the array object, Set it to arrayMethods. At this time, arrayMethods is the prototype object that we rewrite according to the array prototype.
 If it is an object, call the walk method to traverse each value of the object in turn, and set each value to be responsive. Here, the defineReactive method is called, and we will explain it in detail later.
 Then we will describe several methods involved in Observer below:

arrayMethods

 arrayMethods rewrites the internal method of the array, mainly monitors the array method for inserting elements, and makes the inserted elements responsive. For the array method of non-inserting elements, we can restore its original function, and its implementation code is as follows:

// 得到Array.prototype
const arrayPrototype = Array.prototype;

// 以Array.prototype为原型创建arrayMethods对象,并暴露
export const arrayMethods = Object.create(arrayPrototype);

// 要被改写的7个数组方法
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
];

methodsNeedChange.forEach(methodName => {
    
    
    // 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺
    const original = arrayPrototype[methodName];
    // 给我们新创建原型的每个数组的方法设置新的方法逻辑
    // 定义新的方法
    def(arrayMethods, methodName, function () {
    
    
        // 恢复原来的功能
        const result = original.apply(this, arguments);

        // 下面的是我们单独为一些方法做出对应的操作

        // 把类数组对象变为数组
        const args = [...arguments];
        // 把这个数组身上的__ob__取出来,__ob__已经被添加了,
        // 为什么已经被添加了?因为数组肯定不是最高层,
        // 比如obj.g属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,
        // 已经给g属性(就是这个数组)添加了__ob__属性。
        const ob = this.__ob__;

        // 有三种方法push\unshift\splice能够插入新项,现在要把插入的新项也要变为observe的
        let inserted = [];

        // 对插入新项的方法也要编程observe类型的
        switch (methodName) {
    
    
            case 'push':
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                // splice格式是splice(下标, 数量, 插入的新项)
                // slice所截取的是从下标为2一直往后,所以能够获取插入的新项
                inserted = args.slice(2);
                break;
        }

        // 判断有没有要插入的新项,让新项也变为响应的
        if (inserted) {
    
    
            ob.observeArray(inserted);
        }

        return result;
    }, false);
});

 By analyzing the code, we first create a new prototype object from the prototype of the array object, and then we list the methods of the array and rewrite these methods. When traversing through the switch, we reach the push, unshift, and splice functions, and When the number of parameters passed in by the splice function is 3 (indicated as an insertion item), the inserted element will be assigned as the element we inserted. If it is judged that the inserted array is not empty, the inserted array element will be traversed again, and each Items are set to be responsive.

observe

Observe instantiates the object it passes in through the Observer class, then by observing the code of the Observe class before, it can be found that there will be recursive calls here, so that each sub-object is Observerized. For example, when the current object in the Observer is an array, the observer method will be called, and if it is found in the observer that it is an object, the Observer class will be called for instantiation, so that each sub-object will be observerized.


import Observer from './Observer.js';

// value是
export default function (value) {
    
    
    // 如果value不是对象,什么都不做
    if (typeof value != 'object') return;
    // 定义ob
    var ob;
    // __ob__是存储Observer实例的
    // 如果value身上有__ob__的话则直接赋值为ob并进行返回
    if (typeof value.__ob__ !== 'undefined') {
    
    
        ob = value.__ob__;
    } else {
    
    
        // 如果没有则进行初始化进行返回
        ob = new Observer(value);
    }
    return ob;
}

defineReactive

 Add responsiveness to the properties of the object and perform data hijacking. We can also perform some operations through the getter and setter. We will explain later that we collect dependencies when the getter is used, and we call the notify function in the setter function. To notify dep to update, the code is as follows:

export default function defineReactive(data, key, val) {
    
    
    // 创建dep实例
    const dep = new Dep();
    // console.log('我是defineReactive', key);
    if (arguments.length == 2) {
    
    
        val = data[key];
    }

    // 子元素要进行observe,至此形成了递归。这个递归不是函数自己调用自己,而是多个函数、类循环调用
    let childOb = observe(val);

    Object.defineProperty(data, key, {
    
    
        // 可枚举
        enumerable: true,
        // 可以被配置,比如可以被delete
        // configurable特性表示对象的属性是否可以被删除,
        // 以及除value和writable特性外的其他特性是否可以被修改。
        configurable: true,
        // getter
        get() {
    
    
            console.log('你试图访问' + key + '属性');
            // 如果现在处于依赖收集阶段
            if (Dep.target) {
    
    
                dep.depend();
                if (childOb) {
    
    
                    childOb.dep.depend();
                } 
            }
            return val;
        },
        // setter
        set(newValue) {
    
    
            console.log('你试图改变' + key + '属性', newValue);
            if (val === newValue) {
    
    
                return;
            }
            val = newValue;
            // 当设置了新值,这个新值也要被observe
            childOb = observe(newValue);
            
            // 发布订阅模式,通知dep
            dep.notify();
        }
    });

};

Dep

 Dep is a bridge linking Observer and Watcher. Each Observer corresponds to a Dep. It maintains an array internally to store the corresponding
 implementation code of Watcher related to the Observer:

// 闭包
var uid = 0;
// Dep是链接Observer和Watcher的桥梁,每一个Observer对应一个Dep,他内部维护一个数组,保护
// 与该Obverser相关的Watcher
export default class Dep {
    
    
    constructor() {
    
    
        console.log('我是DEP类的构造器');
        // 每创建一个就创建一个唯一的ID值
        this.id = uid++;

        // 用数组存储自己的订阅者。subs是英语subscribes订阅者的意思。
        // 这个数组里面放的是Watcher的实例

        // Watcher是我们所说的订阅者
        this.subs = [];
    }

    // 添加订阅
    addSub(sub) {
    
    
        // 将sub推入数组中
        this.subs.push(sub);
    }

    // 收集依赖
    // 添加依赖
    depend() {
    
    
        // Dep.target就是一个我们自己指定的全局的位置,你用window.target也行,只要是全剧唯一,没有歧义就行
        if (Dep.target) {
    
    
            // 就把相对应的watcher添加进来
            this.addSub(Dep.target);
        }
    }

    // 通知更新
    notify() {
    
    
        console.log('我是notify');
        // 浅克隆一份
        const subs = this.subs.slice();
        // 遍历
        for (let i = 0, l = subs.length; i < l; i++) {
    
    
            // 执行watcher的update函数,使其调用getAndInvoke,执行对应的回调函数进行更新操作
            subs[i].update();
        }
    }
};

 For Dep, it is a bridge linking Observer and Watcher. When collecting dependencies, we add the corresponding subscribers to the Dep object. When the Observer triggers the setter, if the new value is different from the old value, then call the Dep instance. The notify method notifies all subscribers (Watchers) in the instance to call the update method to notify the component to update.

Watcher

The role played by Watcher is subscriber/observer. Its main function is to provide callback functions for observed properties and collect dependencies (such as computed properties, Vue will add the dep of the data that the property depends on to its own deps). When the observed value changes, it will receive a notification from dep, thus triggering the callback function.
Below we post the implementation code:

import Dep from "./Dep";

var uid = 0;
// Watcher是一个订阅者身份,当监听的数据值修改时,
// 执行响应的回调函数,在Vue里面的更新模板内容
export default class Watcher {
    
    
    // target代表需要监听哪个对象
    // expression代表表达式,paresePath可以将其按.进行拆分
    // callback是代表其回调函数
    constructor(target, expression, callback) {
    
    
        console.log('我是Watcher类的构造器');
        this.id = uid++;
        this.target = target;

        this.getter = parsePath(expression);

        this.callback = callback;

        // 获取目标对象的value值
        this.value = this.get();
    }

    update() {
    
    
        this.run();
    }

    get() {
    
    
        // 进入依赖收集阶段。让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段
        Dep.target = this;

        // 获取传进来的目标对象
        const obj = this.target;

        var value;
        // 只要能找,就一直找
        try {
    
    
            // 获取对象根部的value值
            value = this.getter(obj);
        } finally {
    
    
            // 将全局Dep对象为null
            Dep.target = null;
        }

        return value;
    }

    run() {
    
    
        this.getAndInvoke(this.callback);
    }

    //得到并且唤起
    getAndInvoke(cb) {
    
    

        // 获取目标对象的value值
        const value = this.get();
        
        // 如果是值类型,如果之前的值不等于当前的值则执行
        // 如果为对象类型也执行对应的回调函数

        if (value !== this.value || typeof value == 'object') {
    
    
            const oldValue = this.value;
            this.value = value;
            // this指向设置为这个传入的对象,传参为newValue与oldValue
            // 执行更新操作
            cb.call(this.target, value, oldValue);
        }
        
    }
};


function parsePath(str) {
    
    

    // 通过.来进行解析
    var segments = str.split('.');

    return (obj) => {
    
    
        for (let i = 0; i < segments.length; i++) {
    
    
            if (!obj) return;
            // 比如传入 a.b.c.d
            // 这一步能逐渐剥得a:{b:{c:{d:55}}},下一步是b:{c:{d:55}},然后是c:{d:55}等等,直到可以获取目标值
            obj = obj[segments[i]]
        }
        return obj;
    };

}

 From the code analysis, it can be seen that in the Watcher class, the expression represents the hierarchical attribute value of the object that we gradually access. For example: "abcd", it means that we need to access the sub-object d under the a object. This is parsed through the parsePath function. At the beginning of calling the Watcher We call the get method, which makes Dep.target set as the current subscriber, and then calls the getter method. When the getter method is called, the data hijacking of defineProperty in the defineReactive function will be triggered at this time, which will trigger the get function. The function will judge whether Dep.target (an observer) exists, and if it exists, it will collect dependencies, because each monitored data has a Dep instance, then the corresponding Watcher will be added to the monitored data ( Observer) in the Dep instance object for dependency collection, a piece of data will be subscribed by multiple subscribers, and placed in the Dep list uniformly.

Summarize

 Observe is used to monitor data, and Dep is a subscriber that stores multiple subscribers and notifies the subscribers stored in the instance to update. Watcher is a subscriber/observer, which is stored by the instance object of Dep in Observe. When the data in Observe changes, the notification component is updated.
 When the monitored data is valued (getter), if there is Dep.target (an observer), it means that the current observer (Watcher) is dependent on the data, and the current observer will be added to Observer In the subs array of the Dep instance in the Dep instance, wait for the notification of subsequent data changes.
 When the monitored data is assigned (setter), the notify method of the Dep instance object in the Observer will be triggered at this time, which is used to notify all subscribers in the Dep instance object. update (to notify the component to update).
Below we put a legend to better illustrate the relationship between the three.
insert image description here
 This is the end of the article shared this time, welcome to communicate in the discussion area~

Vue source code series articles:

Guess you like

Origin blog.csdn.net/liu19721018/article/details/125435941