• Vue 响应式原理


    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM树上。

    • 虚拟DOM (Virtaul DOM): 用 js 对象模拟的,保存当前视图内所有 DOM 节点对象基本描述属性和节点间关系的树结构。用 js 对象,描述每个节点,及其父子关系,形成虚拟 DOM 对象树结构。
    • 因为只要在 data 中声明的基本数据类型的数据,基本不存在数据不响应问题,所以重点介绍数组和对象在vue中的数据响应问题,vue可以检测对象属性的修改,但无法监听数组的所有变动及对象的新增和删除,只能使用数组变异方法及$set方法。

    可以看到,arrayMethods 首先继承了 Array,然后对数组中所有能改变数组自身的方法,如 pushpop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 pushunshiftsplice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify() 手动触发依赖通知,这就很好地解释了用 vm.items.splice(newLength) 方法可以检测到变化

    总结:Vue 采用数据劫持结合发布—订阅模式的方法,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

    • Observer 遍历数据对象,给所有属性加上 settergetter,监听数据的变化
    • compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

    Watcher 订阅者是 ObserverCompile 之间通信的桥梁,主要做的事情

    • 在自身实例化时往属性订阅器 (dep) 里面添加自己
    • 待属性变动 dep.notice() 通知时,调用自身的 update() 方法,并触发 Compile 中绑定的回调

    Object.defineProperty(),那么它的用法是什么,以及优缺点是什么呢?

    • 可以检测对象中数据发生的修改
    • 对于复杂的对象,层级很深的话,是不友好的,需要经行深度监听,这样子就需要递归到底,这也是它的缺点。
    • 对于一个对象中,如果你新增加属性,删除属性,**Object.defineProperty()**是不能观测到的,那么应该如何解决呢?可以通过Vue.set()Vue.delete()来实现。
    // 模拟 Vue 中的 data 选项 
    let data = {
        msg: 'hello'
    }
    // 模拟 Vue 的实例 
    let vm = {}
    // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
    Object.defineProperty(vm, 'msg', {
      // 可枚举(可遍历)
      enumerable: true,
      // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义) 
      configurable: true,
      // 当获取值的时候执行 
      get () {
        console.log('get: ', data.msg)
        return data.msg 
      },
      // 当设置值的时候执行 
      set (newValue) {
        console.log('set: ', newValue) 
        if (newValue === data.msg) {
          return
        }
        data.msg = newValue
        // 数据更改,更新 DOM 的值 
        document.querySelector('#app').textContent = data.msg
      } 
    })
    
    // 测试
    vm.msg = 'Hello World' 
    console.log(vm.msg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Vue3.x响应式数据原理

    Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

    Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

    判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

    监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

    我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger

    // 模拟 Vue 中的 data 选项 
    let data = {
      msg: 'hello',
      count: 0 
    }
    // 模拟 Vue 实例
    let vm = new Proxy(data, {
      // 当访问 vm 的成员会执行
      get (target, key) {
        console.log('get, key: ', key, target[key])
        return target[key]
      },
      // 当设置 vm 的成员会执行
      set (target, key, newValue) {
        console.log('set, key: ', key, newValue)
        if (target[key] === newValue) {
          return
        }
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
      }
    })
    
    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    Proxy 相比于 defineProperty 的优势

    • 数组变化也能监听到
    • 不需要深度遍历监听

    ProxyES6 中新增的功能,可以用来自定义对象中的操作

    let p = new Proxy(target, handler);
    // `target` 代表需要添加代理的对象
    // `handler` 用来自定义对象中的操作
    // 可以很方便的使用 Proxy 来实现一个数据绑定和监听
    
    let onWatch = (obj, setBind, getLogger) => {
      let handler = {
        get(target, property, receiver) {
          getLogger(target, property)
          return Reflect.get(target, property, receiver);
        },
        set(target, property, value, receiver) {
          setBind(value);
          return Reflect.set(target, property, value);
        }
      };
      return new Proxy(obj, handler);
    };
    
    let obj = { a: 1 }
    let value
    let p = onWatch(obj, (v) => {
      value = v
    }, (target, property) => {
      console.log(`Get '${property}' = ${target[property]}`);
    })
    p.a = 2 // bind `value` to `2`
    p.a // -> Get 'a' = 2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    总结

    • Vue
      • 记录传入的选项,设置 $data/$el
      • data 的成员注入到 Vue 实例
      • 负责调用 Observer 实现数据响应式处理(数据劫持)
      • 负责调用 Compiler 编译指令/插值表达式等
    • Observer
      • 数据劫持
        • 负责把 data 中的成员转换成 getter/setter
        • 负责把多层属性转换成 getter/setter
        • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
      • 添加 DepWatcher 的依赖关系
      • 数据变化发送通知
    • Compiler
      • 负责编译模板,解析指令/插值表达式
      • 负责页面的首次渲染过程
      • 当数据变化后重新渲染
    • Dep
      • 收集依赖,添加订阅者(watcher)
      • 通知所有订阅者
    • Watcher
      • 自身实例化的时候往dep对象中添加自己
      • 当数据变化dep通知所有的 Watcher 实例更新视图
  • 相关阅读:
    Swagger3+knife4j的使用
    Webshell详解
    Zhong__GORM创建或更新(update_or_create)
    Linux命令系列之top——里面藏着很多鲜为人知的宝藏知识
    [学习笔记](b站视频)PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】(ing)
    PLC串口通讯和通讯接口知识汇总
    Spring之ioc
    向函数传递参数(传值、传引用、传const引用)
    IDEA断点调试技巧
    【电商】电商后台设计—电商订单
  • 原文地址:https://blog.csdn.net/php_martin/article/details/125872195