Vue internal operation mechanism to resolve (finishing)

Resolve vue principle of good text:

https://github.com/answershuto/learnVue

https://www.jianshu.com/p/4c47c12d4b3f

 

1, vue operating mechanism

Capture .PNG

In the new Vue (after). Vue will call the init function to initialize, most important of which is set by Object.defineProperty setter and getter function to achieve "responsive" and "dependent collect"

Object.defineProperty ()
method defines an object directly on a new attribute, or modify an existing attribute, and return the object.
Object.defineProperty (obj, prop, descriptor)
parameters
obj: object definition required properties.
prop: the need to define or modify the name of the property.
descriptor: to be defined or modified property descriptor.
Objects in existing attribute descriptor two primary forms: the data access descriptor and the descriptor. It has a data descriptor is writable or unwritable attribute values. Access descriptor is an attribute of a pair of getter-setter function function described herein.
Access data descriptor and a descriptor has the following optional keys:
configurable
if and only if the configurable attribute is true, the attribute descriptor to be able to be changed, can also be deleted. The default is false.
enumerable
if and only if the enumerable attribute is true, the property to be able to appear in the enumeration property of the object. The default is false.
Data descriptor also has the following optional key:
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 writable attribute is true, the properties may be changed by the assignment operator. The default is false.
Access Descriptor (third parameter object) also has the following optional key:
GET
To provide a property getter method, if there is no getter was undefined. When we read a property when, in fact, inside the object calls this method, this method must have a return statement. The return value is used as the method attribute value. The default is undefined.
set
a property to provide setter method, if no setter was undefined. This method takes a unique parameter, and the new value of the parameter assigned to the attribute. The default is undefined. That is, when we set a property when, in fact, inside the object of this method is called.

 

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,       /* 属性可枚举 */
        configurable: true,     /* 属性可被修改或删除 */
        get: function reactiveGetter () {
            return val;         /* 实际上会依赖收集,下一小节会讲 */
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

This method is achieved by Object.defineProperty object of "responsive" technology, to the Senate is a obj (to be bound objects), key (obj of one property), val (specific value). After defineReactive after treatment, our key attributes of obj time in "reading" triggers reactiveGetter method, and the property is "write" when reactiveSetter method is triggered.

After initialization calls $ mount will mount components, a "compilation" step.

Compiler can compile into parse, optimize and generate three stages, ultimately render function needs to be

the parse
the parse parses the template instruction in the template, class, style and other regular data, etc., is formed AST.
optimize
main role is to optimize static mark static nodes, which is an optimization in the compilation process Vue, when later update interface update, there will be a patch process, diff algorithm will skip the static node, thereby reducing the comparative the process of optimizing the performance of the patch.
Generate
Generate the AST is converted to render the process function string, the result is obtained and staticRenderFns render string string.

After experienced parse, optimize and generate these three stages, the assembly will be required to render VNode presence of the render function.

Next we introduce "rely collection" How is implemented.

 

class Dep {
    constructor () {
        /* 用来存放Watcher对象的数组 */
        this.subs = [];
    }

    /* 在subs中添加一个Watcher对象 */
    addSub (sub) {
        this.subs.push(sub);
    }

    /* 通知所有Watcher对象更新视图 */
    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}

You can add a Watcher subscription operation in the current Dep object with addSub method;
all objects Watcher notice Dep currently the object of a notify method subs triggers an update operation.
In the course of the first observer will get registration method used to "rely on the collection." Dep have a target in its closure, this object is used to store the Watcher object instance. In fact, "dependent on the collection" process is the Watcher instance stored in the corresponding object to Dep. get ways to make the current Watcher objects (Dep.target) stored in its subs in (addSub) method, when the data changes, set calls the object's notify method Dep inform all its internal objects Watcher view updates.

Modify the value of the object in time, will trigger a corresponding setter, "dependent on the collection" in each Watcher Dep obtained before setter notification that their own values ​​change, need to re-render the view. This time the Watcher will begin calling update to update the view eventually will VNode node is newly created and old VNode the process of a patch than to produce the "difference", eventually these "differences" to update the view.

We know, render function will be converted into VNode node. Virtual DOM is actually a JavaScript Object (VNode node) as the basis of the tree, object attributes to describe the node, it is actually just a layer of abstraction of the real DOM. Eventually the tree is mapped to the real environment through a series of operations on

First talk about the core of the patch diff algorithm, we use the diff algorithm can compare the two trees of "difference", we look at, say we now have the following two trees, they are old and new VNode node, this time to patch process, and we need them to compare
diff algorithms by comparing tree nodes in the same layer, layer by layer, rather than the tree traversal search of the way, so the only time complexity O (n), is a very efficient algorithm, as shown below.

 

Capture .PNG

 

Squares of the same color in this figure node for comparison, than will get "difference" of these "differences" to update the view. Because carried out only with the level of alignment, it is very efficient.

 

function patch (oldVnode, vnode, parentElm) {
    if (!oldVnode) {
        addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
    } else if (!vnode) {
        removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
    } else {
        if (sameVnode(oldVNode, vnode)) {
            patchVnode(oldVNode, vnode);
        } else {
            removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
            addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
        }
    }
}

首先在 oldVnode(老 VNode 节点)不存在的时候,相当于新的 VNode 替代原本没有的节点,所以直接用 addVnodes 将这些节点批量添加到 parentElm 上。
然后同理,在 vnode(新 VNode 节点)不存在的时候,相当于要把老的节点删除,所以直接使用 removeVnodes 进行批量的节点删除即可。
最后一种情况,当 oldVNode 与 vnode 都存在的时候,需要判断它们是否属于 sameVnode(相同的节点)。如果是则进行patchVnode(比对 VNode )操作,否则删除老节点,增加新节点。

Vue.js 是在我们修改 data 中的数据后修改视图其实就是一个“setter -> Dep -> Watcher -> patch -> 视图”的过程。

假设我们有如下这么一种情况。

 

<template>
  <div>
    <div>{{number}}</div>
    <div @click="handleClick">click</div>
  </div>
</template>
export default {
    data () {
        return {
            number: 0
        };
    },
    methods: {
        handleClick () {
            for(let i = 0; i < 1000; i++) {
                this.number++;
            }
        }
    }
}

当我们按下 click 按钮的时候,number 会被循环增加1000次。

那么按照之前的理解,每次 number 被 +1 的时候,都会触发 number 的 setter 方法,从而根据上面的流程一直跑下来最后修改真实 DOM。那么在这个过程中,DOM 会被更新 1000 次!太可怕了。

Vue.js 肯定不会以如此低效的方法来处理。Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。
因为 number 执行 ++ 操作以后对应的 Watcher 对象都是同一个,我们并不需要在下一个 tick 的时候执行 1000 个同样的 Watcher 对象去修改界面,而是只需要执行一个 Watcher 对象,使其将界面上的 0 变成 1000 即可。
那么,我们就需要执行一个过滤的操作,同一个的 Watcher 在同一个 tick 的时候应该只被执行一次,也就是说队列 queue 中不应该出现重复的 Watcher 对象。...

 

let has = {};
let queue = [];
let waiting = false;

function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);

        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}

number 会被不停地进行 ++ 操作,不断地触发它对应的 Dep 中的 Watcher 对象的 update 方法。然后最终 queue 中因为对相同 id 的 Watcher 对象进行了筛选,从而 queue 中实际上只会存在一个 number 对应的 Watcher 对象。在下一个 tick 的时候(此时 number 已经变成了 1000),触发 Watcher 对象的 run 方法来更新视图,将视图上的 number 从 0 直接变成 1000。
参考于https://juejin.im/book/5a36661851882538e2259c0f



作者:李欢li
链接:https://www.jianshu.com/p/4c47c12d4b3f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发布了178 篇原创文章 · 获赞 28 · 访问量 8万+

Guess you like

Origin blog.csdn.net/sunct/article/details/103689930