zoukankan      html  css  js  c++  java
  • Vue3 为什么要用 Proxy 代替 Object.defineProperty 实现响应式

    Object.defineProperty 劫持数据

    只是对对象的属性进行劫持

    • 无法监听新增属性和删除属性
      需要使用 vue.set, vue.delete
    • 深层对象的劫持需要一次性递归
    var obj = {
      a: 1,
      o: {
        b: 2,
        o1: {}
      }
    }
    • 无法监听原生数组,需要特殊处理,重写覆盖部分 Array.prototype 原生方法。

    Proxy 劫持数据

    真正的对对象本身进行劫持

    • 可以监听到对象新增,删除属性
    • 只在 getter 时才对对象的下一层进行劫持(优化了性能),如访问obj.o,属性o才是响应式, 访问obj.o.b, b才是响应式,访问到那个属性,那个属性才是响应式,不需一次性遍历
    • 能正确监听原生数组方法
    • 无法 polyfill 存在浏览器兼容问题

    Object.defineProperty 实现响应式

    function defineReactive(target, key, value) {
        observer(value) // 对 value 深层监听
        
        Object.defineProperties(target, key, {
            get() {
                // dep.addSubs(watcher) // 添加到监听队列
                return value
            },
            set(newValue) {
                if (newValue !== value) {
                    observer(newValue) // 再次劫持新 value
                    
                    value = newValue
                    // dep.notify() // 通知依赖触发监听队列的更新
                }
            }
        })
    }
    
    function observer(target) {
      if (typeof target !== 'object' || !target) {
        return target
      }
      
      if (Array.isArray(target)) {
        target.__proto__ = newArrProto
      }
      //遍历递归
      for (let key of target) {
        defineReactive(target, key, target[key])
      }
    }
    //创建原生数组原型
    const oldArrProto = Array.prototype
    
    const newArrProto = Object.create(oldArrProto)
    //重写数组方法 [
    'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(methodName => { newArrProto[methodName] = function(...args) { // dep.notify() oldArrProto[methodName].apply(this, args) } })

    Dep 和 Watcher 具体实现可以参考之前的文章 实现响应式原理

    Object.defineProperty 缺点

    • 无法监听新增属性和删除属性
      需要使用 vue.set, vue.delete
    • 深层对象的劫持需要一次性递归
    function observer(target) {
      ...
      for (let key of target) {
        defineReactive(target, key, target[key])
      }
    }
    
    function defineReactive(target, key, value) {
      observer(target) // 首次监听时就对 value 的属性进行递归
      ...
    }
    监听原生数组的部分方法需要重写覆盖 Array.prototype
    ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] 会改变原数组的原生方法不会被 Object.defineProperty 劫持,需要重新写数组的原生方法添加更新触发

    Proxy 实现响应式

    reflect,规范化,标准化,函数式,mdn描述;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
    替代掉Object上的工具函数,

    // 创建响应式
    function reactive(target = {}) {
        if (typeof target !== 'object' || target == null) {
            return target
        }
        
        const proxyConfig = {
            get(target, key, receiver) {
            // 只处理本身(非原型的)属性
                // target是对象,ownKeys是属性的数组
                // target是数组,ownKeys是索引数组,最后一个是length
                const ownKeys = Reflect.ownKeys(target)
                if (ownKeys.includes(key)) {
                  // dep.subs(watcher) // 添加监听
                }
                const result = Reflect.get(target, key, receiver)
              // 深度监听
                // 性能如何提升的?
                return reactive(result) // 只在 getter 时才再次劫持
            },
            set(target, key, val, receiver) {
            // 重复的数据,不处理
              if (val === target[key]) {
                return
              }
                
                const ownKeys = Reflect.ownKeys(target)
            // 判断是否是新增属性
                if (ownKeys.includes(key)) {
                  // 已有值
                } else {
                  // 新增值
                }
                
                const result = Reflect.set(target, key, val, receiver)
                // dep.noitfy() // 通知监听队列进行更新
            // 是否设置成功
                return result
            },
            deleteProperty(target, key) {
                const result = Reflect.deleteProperty(target, key)
                // 是否删除成功
                return result
            }
        }
        
        const observed = new Proxy(target, proxyConfig)
        return observed
    }                            

    Proxy 解决的问题

    • 可以监听到对象新增删除属性
    • 只在 getter 时才对对象的下一层进行劫持(优化了性能)
        get(target, key, receiver) {
                const ownKeys = Reflect.ownKeys(target)
                if (ownKeys.includes(key)) {
                  // dep.subs(watcher) // 添加监听
                }
                const result = Reflect.get(target, key, receiver)
                return reactive(result) // 只在 getter 时才再次劫持
            },
    • 能正确监听原生数组方法

    总结

    Object.defineProperty 是对所有对象属性的劫持,如访问obj.o, 所有属性,包括深层的都是响应式,浪费性能
    Proxy 是对访问到哪个属性,那个属性就是响应式,如访问obj.o,属性o才是响应式, 访问obj.o.b, b才是响应式,访问到那个属性,那个属性才是响应式,不需一次性遍历

    Object.defineProperty 无法监听新增和删除
    Object.defineProperty 无法监听数组部分方法需要重写
    Object.defineProperty 性能不好要对深层对象劫持要一次性递归

    Proxy 能正确监听数组方法
    Proxy 能正确监听对象新增删除属性
    Proxy 只在 getter 时才进行对象下一层属性的劫持 性能优化
    Proxy 兼容性不好

    Object.defineProperty 和 Proxy
    在 getter 时进行添加依赖 dep.addSub(watcher) 比如 绑定 view 层,在函数中使用
    在 setter 时触发依赖通知 dep.notify() 比如 修改属性



    作者:coolheadedY
    原文链接:https://www.jianshu.com/p/8cde476238f0

  • 相关阅读:
    Java网络编程
    IDEA maven-3.6.2 Unable to import maven project错误
    Java通过ArrayList的contains(Object o)方法实现去重字符串中的字符
    常用的Unicode值范围
    Java函数(方法)的默认值问题
    [Noip2017]宝藏
    [Noip2017]组合数问题
    [Noip2018]赛道修建
    [Noip2018]旅行(数据加强版)
    [Noip2019]树的重心
  • 原文地址:https://www.cnblogs.com/fsg6/p/14480132.html
Copyright © 2011-2022 走看看