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

    如何追踪变化

    当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,每一个属性都有一个自己对应的Dep订阅器。
    在对’#app’里面的子元素进行编译时,会对每一个需要获取key的位置创建一个订阅者watcher
    (1)构造时将这个watcher赋值给Dep.newSub,然后读取key,触发get(),在get函数中触发当前key对应的订阅器dep的addSub事件,将这个watcher加入到key的订阅者数组中------订阅key
    (2)每次key改变,就会触发set,从而触发dep的notify事件,通知该key的订阅者数组subs所有成员更新最新数据并把新值通过回调函数传回,更新视图,达到追踪变化的效果------发布消息

    检测变化的注意事项

    受现代 JavaScript 的限制,Vue 无法检测到对象属性的添加或删除。由于 Vue 只在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
    对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

    声明响应式属性

    由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值

    /* 监听器 */
    class Observer {
        constructor(data) {
            this.observer(data);
        }
        /* 遍历data设置监听 */
        observer(obj) {
            if (obj && typeof obj == 'object') {
                /* 获取obj所有子属性 */
                let keys = Object.keys(obj);
                /* 遍历子属性,全部设置监听 */
                keys.forEach(key => {
                    this.defineRactive(obj, key, obj[key]);
                })
            }
        }
        /* 监听 */
        defineRactive(obj, key, val) {
            /* 若子对象则继续遍历 */
            this.observer(val);
            /* 给每个值设置一个订阅器 */
            let dep = new Dep();
            /* 监听改变函数 */
            Object.defineProperty(obj, key, {
                get() {
                    /* 判断是否有新订阅者 */
                    Dep.newSub && dep.addSub();
                    return val;
                },
                set: (newVal) => {
                    /* 相等则没必要更新 */
                    if (val !== newVal) {
                        /* 更新val已有的属性的数据,对新加的属性进行监听 */
                        if (typeof val == 'object') {/* 对象或数组 */
                            /* 更新内部内容 */
                            this.updateObj(val, newVal);
                        } else {
                            val = newVal;
                        }
                        /* 发布 */
                        dep.notify();
                    }
                }
            })
        }
        /* 对象更新设置子数据更新 */
        updateObj(obj1, obj2) {
            if (obj1 instanceof Object || obj1 instanceof Array) {
                for (let key in obj1) {
                    if (obj1[key] instanceof Object || obj1[key] instanceof Array) {
                        this.updateObj(obj1[key], obj2[key]);
                    } else {
                        obj1[key] = obj2[key];
                    }
                }
            } else {
                obj1 = obj2;
            }
        }
    }
    /* 订阅器 */
    class Dep {
        constructor() {
            /* 初始化订阅者数组 */
            this.subs = [];
        }
        /* 增加订阅者 */
        addSub() {this.subs.push(Dep.newSub);}
        /* 发布 */
        notify() {
            /* 通知所有订阅者更新数据 */
            this.subs.forEach(sub => {
                sub.update();
            })
        }
    }
    /* 订阅者 */
    class Watcher {
        constructor(vm, prop, cb) {
            this.vm = vm;
            this.prop = prop;
            this.cb = cb;
            /* 加入订阅者数组 */
            this.join();
        }
        /* 加入订阅者数组 */
        join() {
            /* 将自己设置为订阅器的新订阅者 */
            Dep.newSub = this;
            /* 读取一次数据触发get,从而间接触发addSub */
            this.oldVal = CompileUtil.getVal(this.vm, this.prop);
            /* 重置,否则会重复添加 */
            Dep.newSub = null;
        }
        /* 更新 */
        update() {
            /* 对比新旧值决定是否更新 */
            let newVal = CompileUtil.getVal(this.vm, this.prop);
            if (this.oldVal !== newVal) {
                this.oldVal = newVal;
                /* 运行回调函数 */
                this.cb(newVal);
            }
        }
    }
    

    异步更新队列

    Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
    为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:

    Vue.component('example', {
      template: '<span>{{ message }}</span>',
      data: function () {
        return {
          message: '未更新'
        }
      },
      methods: {
        updateMessage: function () {
          this.message = '已更新'
          console.log(this.$el.textContent) // => '未更新'
          this.$nextTick(function () {
            console.log(this.$el.textContent) // => '已更新'
          })
        }
      }
    })
    
  • 相关阅读:
    python内置模块collections介绍
    Python的set集合详解
    不同 Python 数据类型的搜寻
    Python 分支、循环、条件与枚举
    ssrf爆破mysql
    php反序列化
    thinkphp历史漏洞
    Thinkphp 缓存RCE
    绕WAF文章收集
    mssql手工盲注
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12638598.html
Copyright © 2011-2022 走看看