1. Rendering principle

  • 1. Encapsulate the updated functionality as a Watcher class;
  • 2. Before rendering the page, the current Watcher will be placed on the Dep class;
  • 3. The attributes used in page rendering in Vue need to be collected by dependency collection, collection of object rendering Watcher
  • 4. Add a DEP attribute to each attribute to store the rendered watcher.
  • 5. Each attribute may correspond to multiple views. If multiple views are certain, there are multiple Watchers
  • 6. Dep. Depend () – > notice deposit dep watcher – > dep. Target. AddDep – > notice deposit watcher dep

1.1. Test examples

After 2s the user changes the value of name so that the automatic refresh function is attempted

<body>
    <div id="app" style="color:red; background:green">hello {{name}} world</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {name:'zhangsan'}})setTimeout(() = > {
            vm.name='lisi';
        }, 2000);
    </script>
</body>
Copy the code

1.2.lifecycle.js

import Watcher from "./observer/watcher"; 
import {
    patch
} from "./vdom/patch";

export function lifecycleMixin(Vue) {
    Vue.prototype._update = function (vnode) {
        const vm = this;
        // We need to assign vm.$el to the new virtual DOMvm.$el = patch(vm.$el, vnode); }}/** * Components mount *@param {*} vm 
 * @param {*} el <div id='app'></div>
 */
export function mountComponent(vm, el) {
    /** * TODO: update function * 1. Call _render to generate vDOM * 2. Call _update for the update operation */
    const updateComponent = () = > {
        vm._update(vm._render());
    }

    //true means render watcher
    const watcher=new Watcher(vm, updateComponent, () = > {
        console.log('Update attempt')},true);
}
Copy the code

1.3.observer/watcher.js

Each component has a render Watcher

import {
    popTarget,
    pushTarget
} from "./dep";
let id = 0;
class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm;
        this.exprOrFn = exprOrFn;
        this.cb = cb;
        this.options = options;
        this.id = id++;

        this.getter = exprOrFn;
        this.deps = [];
        this.depsId = new Set(a);this.get(); // Initialization is performed once by default
    }

    get() {
        //Dep.target=watcher
        pushTarget(this);
        this.getter(); //TODO:This.getter ->render() executes, vm values, will be in get
        popTarget();
    }

    update() {
        this.get();
    }
    addDep(dep) {
        // Prevent multiple dePs from being stored in the same watcher
        if (!this.depsId.has(dep.id)) {
            this.depsId.add(dep.id);
            this.deps.push(dep);
            dep.addSub(this); / / dep watcher}}}export default Watcher;
Copy the code

1.4.observer/dep.js

  • 1. Each attribute has a DEP
  • 2. Multiple Watchers are stored in the DEP
let id = 0;
class Dep {
    constructor() {
        this.id = id++;
        this.subs = []; // Save watcher
    }
    depend() {
        if (Dep.target) {
            // Let Watcher mark dep
            Dep.target.addDep(this); }}addSub(watcher) {
        this.subs.push(watcher);
    }
    notify(){
        this.subs.forEach(watcher= >watcher.update());
    }
}

Dep.target = null;

export function pushTarget(watcher) {
    Dep.target = watcher;
}

export function popTarget() {
    Dep.target = null;
}

export default Dep;
Copy the code

1.5.observer/index.js

  • Each property has its own DEP
  • When get, the attribute DEp is related to Watcher
  • Set to notify the watcher stored in the DEP of updates
function defineReactive(data, key, value) {
    observe(value); //TODO:If value is an object, a deeper hijacking of value is required
    //TODO: Each attribute has a DEP attribute
    const dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            if (Dep.target) {// set dep to watcher (); // set dep to watcher
                dep.depend(); // Make deP remember Watcher
            }
            return value;
        },
        set(newVal) {
            if (newVal === value) return;
            observe(newVal); //TODO:The reset value may be an object, at which point it needs to be hijacked again
            value = newVal;
            dep.notify(); // When a value is changed, watcher is notified to execute the current property}})}Copy the code

2. Update data asynchronously

<body>
    <div id="app" style="color:red; background:green">hello {{name}} world</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {name:'zhangsan'}})setTimeout(() = > {
            vm.name='lisi';
            vm.name='111';
            vm.name='2222';
            vm.name='333';
            console.log(vm.$el)
            vm.$nextTick(() = >{
                console.log(vm.$el); })},2000);
    </script>
</body>
Copy the code

2.1.observer/watcher.js

update() {
    // Update the watcher cache
    queueWatcher(this);
}
run() {
    console.log('run')
    this.get();
}
Copy the code

2.2.observer/scheduler.js

import {
    nextTick
} from ".. /utils";
let queues = [];
let has = {};
let pending = false;

function flushSchedulerQueue() {
    for (let i = 0; i < queues.length; i++) {
        queues[i].run(); // Update the view
    }
    queues = [];
    has = {};
    pending = false;
}

export function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) { / / to heavy
        has[id] = true;
        queues.push(watcher);
        if(! pending) {/ / image stabilization
            nextTick(flushSchedulerQueue);
            pending = true; }}}Copy the code

2.3.lifecycle.js

export function lifecycleMixin(Vue) {
    Vue.prototype._update = function (vnode) {
        const vm = this;
        // We need to assign vm.$el to the new virtual DOM
        vm.$el = patch(vm.$el, vnode);
    }
    // When the user calls nextTick himself
    Vue.prototype.$nextTick = function (cb) { nextTick(cb); }}Copy the code

2.4.utils.js

const callbacks = [];
let waiting = false;

function flushCallbacks() {
    callbacks.forEach(cb= > cb());
    waiting = false
}

function timer(flushCallbacks) {
    let timerFn = () = > {};
    if (Promise) { // Whether to support Promise
        timerFn = () = > {
            Promise.resolve().then(flushCallbacks); }}else if (MutationObserver) { // Whether text changes are supported, microtasks
        let textNode = document.createTextNode(0);
        // Listen for text changes
        const observe = new MutationObserver(flushCallbacks);
        observe.observe(textNode, {
            characterData: true
        })
        timerFn = () = > {
            textNode.textContent = 1; }}else if (setImmediate) { // Only IE is supported
        timerFn = () = >{ setImmediate(flushCallbacks); }}else {
        timerFn = () = > {
            setTimeout(flushCallbacks);
        }
    }
    timerFn();
}
export function nextTick(cb) {
    /** * TODO: * 1. NextTick * 2 for attribute assignment. The user invokes vm.$nextTick */
    callbacks.push(cb);
    if(! waiting) {// In vue2 compatibility is considered, in VU3 direct promise.resolve ().then()
        timer(flushCallbacks);
        waiting = true; }}Copy the code

3. Principle of array update

  • 1. The nesting level in Vue should not be too deep, otherwise there will be a lot of recursion
  • 2. The object in Vue is reactive via defineProperty implementation, intercepting GET and set. Properties that do not exist are not intercepted and will not respond. You can notify the object itself using $set, or assign a new object to it
  • 3. Changing the index and length of the array in Vue will not affect the update. The view can be updated by mutating 7 methods

3.1. One-dimensional arrays

Test 3.1.1.
<body>
    <div id="app" style="color:red; background:green">{{arr}}</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {arr: [1.2.3]}})setTimeout(() = > {
           vm.arr.push(4);
        }, 2000);
    </script>
</body>
Copy the code
3.1.2.observer/index.js
class Observer {
    constructor(data) {
        //TODO:Add deP attributes to the outer data
        this.dep = newDep(); }}function defineReactive(data, key, value) {
    const childOb = observe(value); //TODO:If value is an object, a deeper hijacking of value is required
    Object.defineProperty(data, key, {
        get() {
            if (Dep.target) {
                dep.depend(); // Make deP remember Watcher
                / * * *TODO:ChildOb =new Observe() * 2 if value is arr. $set = $set */; $set = $set */
                if (childOb) {
                    childOb.dep.depend(); // Array/object record watcher}}returnvalue; }})}export function observe(data) {
    //TODO:Data must be an object, and the default outermost layer must be an object
    if(! isObject(data))return;
    // If the observed data already has an __ob__ attribute, the data has already been hijacked and no longer needs to be hijacked
    if (data.__ob__) return data.__ob__;
    return new Observer(data);
}
Copy the code
3.1.3.observer/array.js
methods.forEach(method= > {
    // If the user calls the above 7 methods, it will go through its own method first
    arrayMethods[method] = function (. args) {
        // This.__ob__ is an Observer instance.
        if (inserted) this.__ob__.observeArray(inserted);
        //TODO: The array notifies the view of updates through the outer DEP when the 7 methods are called
        this.__ob__.dep.notify(); }})Copy the code

3.2. Multidimensional arrays

3.2.1. The test
<body>
    <div id="app" style="color:red; background:green">{{arr}}</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {arr: [[1.2.3]]}})setTimeout(() = > {
           vm.arr[0].push(4);
        }, 2000);
    </script>
</body>
Copy the code
3.2.2.observer/index.js
function dependArray(value) {
    for (let i = 0; i < value.length; i++) {
        const current = value[i];
        current.__ob__ && current.__ob__.dep.depend();
        if (Array.isArray(current)) { dependArray(current); }}}/ * * *TODO:Why is the performance of Vue2 poor, the main reason is data hijacking full hijacking *@param {*} Data Raw data *@param {*} key key
 * @param {*} The value value * /
function defineReactive(data, key, value) {
    const childOb = observe(value); //TODO:If value is an object, a deeper hijacking of value is required
    //TODO: Each attribute has a DEP attribute
    const dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            if (Dep.target) {
                dep.depend(); // Make deP remember Watcher
                / * * *TODO:ChildOb =new Observe() * 2 if value is arr. $set = $set */; $set = $set */
                if (childOb) {
                    childOb.dep.depend(); // Array/object record watcher
                    if (Array.isArray(value)) { dependArray(value); }}}returnvalue; }}Copy the code