zoukankan      html  css  js  c++  java
  • 从 defineProperty 到 Proxy

    众所周知,Vue 2.x 的数据绑定是通过 defineProperty。而在 Vue 3.x 的设计中,数据绑定是通过 Proxy 实现的,这两者到底有何异同?

     

    一、definePropety

    defineProperty 是 Object 的一个方法,可以在对象上新增或编辑某个属性,可编辑的内容除了属性值 value 之外,还有该属性的描述信息

    Object.defineProperty(obj, prop, descriptor)

    该方法接收三个参数,分别是目标对象 obj,被编辑的属性名 prop,以及该属性的描述 descriptor

    需要注意的是,只能在 Object 构造器对象使用该方法,实例化的 object 类型是没有该方法的

     

     

    1. 基础描述符

    • configurable当该键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。

    当该描述符为 false 的时候,其它的描述符一旦定义,就无法再更改,且该属性无法被 delete 删除

     

    • enumerable当该键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。

    当 enumerable 为 false 时,Objcet.keys() 和 for...in 都无法获取到被定义的属性

    但 Reflect.ownKeys() 可以...

      

    2. 数据描述符

    • value属性值。可以是任何有效的 JavaScript 值 (数值,对象,函数等)。默认为 undefined。
    • writable当该键值为 true 时,属性的值(即 value)才能被赋值运算符改变。 默认为 false。

     

    3. 存取描述符

    • get:该属性的 getter 函数,访问该属性时候会调用该函数,其返回值会被用作 value,默认为 undefined。

    该函数没有入参,但是可以使用 this 对象,只是这个 this 不一定是源对象 obj

     

    • set: 该属性的 setter 函数,当属性值被修改时,会调用此函数,默认为 undefined。

    该方法接受一个参数,即被赋予的新值,同时会传入赋值时的 this 对象

     

    ⚠️注意:数据描述符和存取描述符不可同时存在!

      

    4. Vue 2.x 响应式原理

    在 Vue 2.x 中其实就是在观察者模式中使用上面提到的 get 和 set 实现的数据绑定

    首先实现依赖收集和 Watcher

    // 通过 Dep 解耦属性的依赖和更新操作
    class Dep {
      constructor() {
        this.subs = []
      }
      // 添加依赖
      addSub(sub) {
        this.subs.push(sub)
      }
      // 更新
      notify() {
        this.subs.forEach(sub => {
          sub.update()
        })
      }
    }
    // 全局属性,通过该属性配置 Watcher
    Dep.target = null
    
    class Watcher {
      constructor(obj, key, up) {
        // 手动触发 getter 以添加监听
        Dep.target = this
        this.up = up
        this.obj = obj
        this.key = key
        this.value = obj[key]
        // 完成依赖添加后重置 target
        Dep.target = null
      }
      update() {
        // 获得新值
        this.value = this.obj[this.key]
        // 调用 update 方法更新 Dom
        this.up(this.value)
      }
    }

    然后通过 defineProperty 来实现响应

    function observe(obj) {
      if (!obj || typeof obj !== 'object') {
        return
      }
      Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
      })
    }
    
    function defineReactive(obj, key, val) {
      // 递归子属性
      observe(val)
      let dp = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
          // 将 Watcher 添加到订阅
          if (Dep.target) {
            dp.addSub(Dep.target)
          }
          return val
        },
        set(newVal) {
          val = newVal
          // 执行 watcher 的 update 方法
          dp.notify()
        }
      })
    }

    完成之后,通过 observe 遍历对象,然后实例化 Watcher,手动触发一次 getter 完成数据绑定

    const data = { name: '' }
    observe(data)
    function update(value) {
      document.body.innerHTML = `<div>${value}</div>`
    }
    // 模拟解析到 `{{name}}` 触发的操作
    new Watcher(data, 'name', update)
    data.name = 'Wise.Wrong'

    这部分代码参考自掘金小册《前端面试之道》

    二、Proxy

    以 Object.defineProperty() 实现的响应式有两个问题:

    1. 给对象新增属性并不会更新 DOM;

    2. 以索引的方式修改数组也不会触发 DOM 的更新。

    最终 Vue 是通过重写函数的方式解决了这两个问题,但对于数组的数据绑定依然有瑕疵

    而这些问题,对于 Proxy 来说都不是问题

    1. 简介

    const p = new Proxy(target, handler)

    这里的目标对象 target 可以是任何类型的对象,包括原生数组,函数,甚至另一个 Proxy

    而对应的处理器对象 handler 包含很多的 trap 方法,这些 trap 方法会在 Proxy 对象执行对应操作时触发

    下面会介绍几个常用的方法

    getPrototypeOf() Object.getPrototypeOf 方法对应的钩子函数
    setPrototypeOf() Object.setPrototypeOf 方法对应的钩子函数
    defineProperty() Object.defineProperty 方法对应的钩子函数
    has() in 操作符对应的钩子函数
    deleteProperty() delete 操作符对应的钩子函数
    apply() 函数被调用时的钩子函数
    construct() new 操作符对应的钩子函数
    get() 属性读取操作的钩子函数
    set() 属性被修改时的钩子函数

    钩子函数会在对 Proxy 对象执行相应操作的时候触发

     

    2. 钩子函数

    以 set 和 get 为例

    function update(value = 'wise.wrong') {
      console.log('update');
      document.body.innerHTML = value;
    };
    
    const data = ['who', 'am', 'i'];
    
    const subject = new Proxy(data, {
      get: function(obj, prop) {
        return obj[prop];
      },
      set: function(obj, prop, value) {
        update(value);
        obj[prop] = value;
      }
    });

    上面的目标的对象是一个数组,然后实例化 Proxy 的时候添加了 set 的钩子函数

    当 Proxy 对象 subject 被修改的时候,会执行 update 方法

    基于这些钩子函数,就可以参考上面 Object.defineProperty() 的思路实现数据绑定了,而且还不会有上面的遗留问题

    3. 和 defineProperty 的区别

    defineProperty 需要针对具体的 key 设置 getter 和 setter

    Object.defineProperty(obj, prop, descriptor)

    以至于 Vue 2.x 在初始化的时候,需要递归遍历对象的子属性,挨个儿挂载 setter

    这也导致了无法直接通过 defineProperty 实现在对象中新增属性时更新 DOM

    但 Proxy 是针对整个对象的代理,不会关心具体的 key

    而且 Proxy 的目标对象并没有类型限制,除了 Object 之外,还天然支持 Array、Function 的代理

    此外 Proxy 还不仅仅支持 getter 和 setter,上面提到的钩子函数 ,在特定的场景下会发挥出应有的作用

    所以 Proxy 比 Object.defineProperty()  的层次更高,毕竟 defineProperty 只是一个方法,而 Proxy 是一个可实例化的类

     

  • 相关阅读:
    pyhon3 mysql的下载和配置
    python3 进程池和线程池
    python3 udp socketserver
    python3 多进程实现socket通讯
    python3 在一个进程内开启多个线程实现socket通讯
    python3 单线程(gevent)实现并发的socket通讯
    python3 协程gevent模块(遇到IO自动切换)
    python3 greenlet模块
    python3 协程函数yeild
    python3 线程队列queue
  • 原文地址:https://www.cnblogs.com/wisewrong/p/12630627.html
Copyright © 2011-2022 走看看