使用Proxy实现简单的双向绑定

461 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

了解Vue的数据劫持

  1. Vue采用什么进行数据劫持?

    • vue2 : Object.defineProperty
    • vue3 : Proxy
  2. 为什么要取代?

    Object.defineProperty 监听不到数组变化(除vue重写的数组的8个方法)

this.arr[0] = 'Hello world' //监听不到
Vue.$set()     // 使用$set()可以解决
  1. Vue3采用Proxy劫持数据

    • 数据劫持
    • 观察者 Observer
    • 数据解析 Compare

Proxy是什么?

MDNProxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

通俗理解的话,就是在目标对象之前设置一层“拦截”,当外界访问该对象的时候,都必须先通过这层拦截,因此可以对外界的访问进行过滤和改写。

Proxy代理的是谁?

Proxy代理的是数据,这里的数据可以是对象 / 数组 / 方法 (Object.defineProperty只能代理对象)。

实现简单的双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>实现简单的双向绑定</title>
</head>

<body>
  <div>
    <input type="text" id="input">
    <p id="show"></p>
  </div>

  <script>
    let obj = {}
    const input = document.getElementById('input')
    // 设置代理
    let newObj =  new Proxy(obj,{
      set(target,key,value){
        // vue3里面此段代码采用 发布订阅的模式 传递到订阅者
        // 调用diff算法  对比改变 更新
        if (key === 'text') {
            input.value = value;
            show.innerHTML = value
        }
        // this.subtribe.emit('',value)
        return Reflect.set(target,key,value)
      },
      get(target,key){
        return Reflect.get(target,key)
      }
    })

    input.addEventListener('keyup',function(e){
      newObj.text = e.target.value
    })
  </script>
</body>
</html>

Proxy有13种拦截方法

interface ProxyHandler<T extends object> {
    /**
     * A trap method for a function call.
     * @param target The original callable object which is being proxied.
     */
    apply?(target: T, thisArg: any, argArray: any[]): any;

    /**
     * A trap for the `new` operator.
     * @param target The original object which is being proxied.
     * @param newTarget The constructor that was originally called.
     */
    construct?(target: T, argArray: any[], newTarget: Function): object;

    /**
     * A trap for `Object.defineProperty()`.
     * @param target The original object which is being proxied.
     * @returns A `Boolean` indicating whether or not the property has been defined.
     */
    defineProperty?(target: T, property: string | symbol, attributes: PropertyDescriptor): boolean;

    /**
     * A trap for the `delete` operator.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to delete.
     * @returns A `Boolean` indicating whether or not the property was deleted.
     */
    deleteProperty?(target: T, p: string | symbol): boolean;

    /**
     * A trap for getting a property value.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to get.
     * @param receiver The proxy or an object that inherits from the proxy.
     */
    get?(target: T, p: string | symbol, receiver: any): any;

    /**
     * A trap for `Object.getOwnPropertyDescriptor()`.
     * @param target The original object which is being proxied.
     * @param p The name of the property whose description should be retrieved.
     */
    getOwnPropertyDescriptor?(target: T, p: string | symbol): PropertyDescriptor | undefined;

    /**
     * A trap for the `[[GetPrototypeOf]]` internal method.
     * @param target The original object which is being proxied.
     */
    getPrototypeOf?(target: T): object | null;

    /**
     * A trap for the `in` operator.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to check for existence.
     */
    has?(target: T, p: string | symbol): boolean;

    /**
     * A trap for `Object.isExtensible()`.
     * @param target The original object which is being proxied.
     */
    isExtensible?(target: T): boolean;

    /**
     * A trap for `Reflect.ownKeys()`.
     * @param target The original object which is being proxied.
     */
    ownKeys?(target: T): ArrayLike<string | symbol>;

    /**
     * A trap for `Object.preventExtensions()`.
     * @param target The original object which is being proxied.
     */
    preventExtensions?(target: T): boolean;

    /**
     * A trap for setting a property value.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to set.
     * @param receiver The object to which the assignment was originally directed.
     * @returns `A `Boolean` indicating whether or not the property was set.
     */
    set?(target: T, p: string | symbol, newValue: any, receiver: any): boolean;

    /**
     * A trap for `Object.setPrototypeOf()`.
     * @param target The original object which is being proxied.
     * @param newPrototype The object's new prototype or `null`.
     */
    setPrototypeOf?(target: T, v: object | null): boolean;
}

针对数据的拦截

  • get
  • set
  • has
  • deleteProperty

遍历

  • ownKeys

属性描述器

  • getOwnPropertyDescriptor
  • defineProperty 设置修饰器

是否可以拓展

  • preventExtensions:阻止扩展
  • isExtensibe:是否可扩展