zoukankan      html  css  js  c++  java
  • Vue——响应式原理

    Vue的MVVM思想中,主要是靠VM 视图-模型完成响应,充当数据与视图之间的桥梁,数据更新响应视图、视图文本数据更新响应数据。

    • 数据劫持
    • 发布订阅

      数据劫持指的是vue利用ES5的Object.defineProperty属性对data选项中的数据进行getter和setter设置;
      发布订阅指 的是vue通过自定义事件将data的变化反应到视图上去,vue通过observe观察者对象反应数据的变化,然后通知vue生成新的vdom,进而渲染视图。

    一、如何实现Object.defineProperty

      Vue通过设定对象属性的getter/setter 方法来监听数据的变化。通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

    function observe(value, cb) {
        Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
    }
    
    function defineReactive (obj, key, val, cb) {
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                /*....依赖收集等....*/
                return val
            },
            set:newVal=> {
                val = newVal;
                cb();/*订阅者收到消息的回调*/
            }
        })
    }
    
    class Vue {
        constructor(options) {
            this._data = options.data;
            observe(this._data, options.render) //循环遍历每一个data的数据、进行依赖收集和观察
        }
    }
    
    let app = new Vue({
        el: '#app',
        data: {
            text: 'text',
            text2: 'text2'
        },
        render(){
            console.log("render");
        }
    })

     二、依赖收集(为什么不是二维数组?)

    先看下这段代码:

    new Vue({
        template: 
            `<div>
                <span>text1:</span> {{text1}}
                <span>text2:</span> {{text2}}
            <div>`,
        data: {
            text1: 'text1',
            text2: 'text2',
            text3: 'text3'
        }
    });

    按照响应式原理,数据text3被修改的时候也会触发setter导致出现渲染,这显然是不正确的。(因为没有数据依赖)。

    1.先说说Dep,订阅者列表

      当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。

    定义一个依赖收集类Dep。(subs:二维数组,键值对,每个键名都有对应的一张订阅者表。)

    class Dep {
        constructor () {
            this.subs = [];
        }
    
        addSub (sub: Watcher) {
            this.subs.push(sub)
        }
    
        removeSub (sub: Watcher) {
            remove(this.subs, sub)
        }
        notify () {
            const subs = this.subs.slice()
            for (let i = 0, l = subs.length; i < l; i++) {
                subs[i].update()
            }
        }
    }
    function remove (arr, item) {
        if (arr.length) {
            const index = arr.indexOf(item)
            if (index > -1) {
                return arr.splice(index, 1)
            }
        }
    }

    2.订阅者 watcher

      当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。

    class Watcher {
        constructor (vm, expOrFn, cb, options) {
            this.cb = cb;
            this.vm = vm;
    /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/ Dep.target = this; /*Github:https://github.com/answershuto*/ /*触发渲染操作进行依赖收集*/ this.cb.call(this.vm); } update () { this.cb.call(this.vm); } }

    3.开始收集依赖

    class Vue {
        constructor(options) {
            this._data = options.data;
            observer(this._data, options.render);
            let watcher = new Watcher(this, );
        }
    }
    
    function defineReactive (obj, key, val, cb) {
        /*在闭包内存储一个Dep对象*/
        const dep = new Dep();
    
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                if (Dep.target) {
                    /*Watcher对象存在全局的Dep.target中*/
                    dep.addSub(Dep.target);
                }
            },
            set:newVal=> {
                /*只有之前addSub中的函数才会触发*/
                dep.notify();
            }
        })
    }
    Dep.target = null;

      将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。

    三、总结

      首先说一下template解析成AST以及render function的过程:主要是通过正则解析模板成为AST树,然后会将AST树编译成render function,其中运用来缓存等优化方法都是值得一读的。

      接着从Data开始看,当数据Data发生变化时,会触发setter,setter会触发闭包中的Dep通知所有对该数据进行观察的观察者对象Watcher,Watcher会调用_update来更新视图,_update的第一个参数是一个render函数,然后返回一个VNode节点。

      新的VNode节点会与之前的VNode节点进行一个patch的过程,比较得出最小单位的修改。最后将这些修改渲染到真实DOM上。

  • 相关阅读:
    轻重搭配
    EF的优缺点
    使用bootstrap-select有时显示“Nothing selected”
    IIS发布 HTTP 错误 500.21
    js添加的元素无法触发click事件
    sql server查看表是否死锁
    sql server把一个库表的某个字段更新到另一张表的相同字段
    SQLSERVER排查CPU占用高的情况
    SQL server中如何按照某一字段中的分割符将记录拆成多条
    LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.
  • 原文地址:https://www.cnblogs.com/jiox/p/14540312.html
Copyright © 2011-2022 走看看