Vue3响应式系统

1、响应式系统介绍

  1. Proxy 对象实现属性监听

  2. 多层属性嵌套,在访问属性过程中处理下一级属性

  3. 默认监听动态添加的属性

  4. 默认监听属性的删除操作

  5. 默认监听数组索引和 length 属性

  6. 可以作为单独的模块引用

核心方法:
  • reactive / ref / toRefs / computed
  • effect
  • track
  • trigger

2、Proxy对象基本使用

语法:const p = new Proxy(target, handler)

const jiayin = new Proxy(data, {
    // target     目标对象
    // property   被获取的属性名
    // receiver   Proxy或者继承Proxy的对象
    get(target, key, receiver) {
        console.log("get---", key, receiver);
        return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
        console.log("set---", key, value, receiver);
        return Reflect.set(target, key, receiver); // 在严格模式下,若set方法返回false,则会抛出一个 TypeError 异常。
    },
    deleteProperty(target, key) {
        console.log("delete---", target, key);
        return Reflect.deleteProperty(target, key);
    },
});
  1. setdeleteProperty 需要返回布尔类型的值,严格模式下,如果返回 false 的话会出现 Type Error 的异常;

  2. 如果 handler 没有设置任何拦截,那就等同于直接通向原对象 var proxy = new Proxy({}, {})

  3. 关于 Reflect,一开始有疑问,为什么不直接在 Proxy 内获取到值,非要多此一举使用 Reflect。后来我的理解是,Reflect 对象的方法与 proxy 对象的方法一一对应,且 Reflect 对象用起来更好,所以采用,并不是 proxy 的用不了,也不知道这个理解是否正确。

    Reflect 对象有 4 个意义:

    • 操作对象出现报错时返回false,而不是报错
    • 和 Proxy 一一对应
    • 规范化,标准化,函数式(最好删除元素啥的都用这个 API, 不要用 delete obj.key)
    • 代替 Object 上的工具函数
  4. Proxy 和 Reflect 中使用的 receiver

    【这个问题依然没有搞懂!!!,不知道 receiver 到底有什么用】

    Proxy 中 receiver —— Proxy 或者继承 Proxy 的对象

    Reflect 中 receiver —— 如果 target 对象中设置了 getter, getter 中的 this 指向 receiver

    const obj = {
        name: "kkk",
        get foo() {
            console.log("this-->", this); // 这个this指向receiver
            return this.bar;
        },
    };
    
    const proxy = new Proxy(obj, {
        get(target, key, receiver) {
            if (key === "bar") {
                return "value - bar";
            }
            return Reflect.get(target, key, receiver);
        },
    });
    console.log(proxy.foo); // value - bar
    

3、reactive实现

前置知识:如何判断对象是否有某个属性

(1)点( . )或者方括号( [ ] )
  • 如果不存在,返回 undefined (这里的**“不存在”指的是对象自身和原型链上都不存在**,如果原型链有该属性,则会返回原型链上的属性值)
  • 局限:如果值本身为 undefined,这个方法就不能判断了。
(2)in 运算符
  • key in obj 如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true, 值为 undefined 也可以判断
  • 局限:无法区分是自身属性还是原型链上的属性。在只需要判断自身属性是否存在时,这种方式就不适用了
(3)Reflect.has(target, propertyKey)

和 in 运算符 的功能完全相同

(4)hasOwnProperty
  • 返回一个布尔值,指示对象自身属性中是否具有指定的属性,忽略掉那些从原型链上继承到的属性;

  • 局限:JavaScript 并没有保护 hasOwnProperty 这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty 获得正确的结果

    // 举例:foo 对象占用了 hasOwnProperty, 且始终返回 false
    var foo = {
        hasOwnProperty: function () {
            return false;
        },
        bar: "Here be dragons",
    };
    
    foo.hasOwnProperty("bar"); // 始终返回 false
    
    Reflect.has(foo, 'hasOwnProperty') // true
    
    // 如果担心这种情况,
    // 可以直接使用原型链上真正的 hasOwnProperty 方法
    ({}.hasOwnProperty.call(foo, "bar")); // true
    
    // 也可以使用 Object 原型上的 hasOwnProperty 属性
    // 只有这种方法不会重建对象
    Object.prototype.hasOwnProperty.call(foo, "bar"); // true
    
总结:

最好使用 Object.prototype.hasOwnProperty.call(foo, "bar"), 这样可以保证用的是当前对象原型上的 hasOwnProperty 方法,不会使用自身的了。

vue3中如果使用 Reflect.has(target, key) 会导致问题,就是 delete obj.hasOwnProperty 的时候,尽管target对象没有 hasOwnProperty 这个属性,也会执行

代码实现

  1. 新建一个 reactive 函数,接收参数 data

  2. 几个工具函数:

    • 判断 data 是否为对象 const isObject = value => typeof value === 'object' || value === null
    • 嵌套对象,判断是否需要递归 const convert = target => isObject(target) ? reactive(target) : target
    • 判断对象中是否拥有该属性 const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)
  3. 创建拦截器对象 handler, 设置 get / set / deleteProperty

  4. 返回 Proxy 对象

// 判断是否是对象
const isObject = (value) => typeof value === "object" || value === null;
// 如果对象是嵌套状态,就递归处理
const convert = (target) => (isObject(target) ? reactive(target) : target);
// 判断对象中是否有该属性
// const hasOwnProperty = (target, key) => target.hasOwnProperty(key) // 这个方法并不好
const hasOwn = (target, key) =>
  Object.prototype.hasOwnProperty.call(target, key);

export const reactive = (data) => {
  if (!isObject(data)) return data;

  const handler = {
    get(target, key, receiver) {
      // 收集依赖
      console.log("get--", key);
      const res = Reflect.get(target, key, receiver);
      return convert(res);
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver); // 获取旧值与新值对比
      let res = true; // 定义返回的布尔变量
      if (oldValue !== value) {
        console.log("set--", key);
        res = Reflect.set(target, key, value, receiver);
        // 触发更新
      }
      return res;
    },
    deleteProperty(target, key) {
      const hasKey = hasOwn(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (hasKey && res) {
        // 触发更新
        console.log("delete---", key);
      }
      return res;
    },
  };

  return new Proxy(data, handler);
};

Vue3比Vue2性能更好的一个点:

  • Vue2是在一开始就递归遍历了所有层级数据;
  • Vue3则是在 get 的时候才遍历的;

4、ref 实现

前置知识:js 中的 get、set 方法
/*
- obj中的name是数据属性
- get、set后的age属性是访问器属性
访问器属性:当外部js给age赋值时走的时setter函数,当外部js获取age时 走的getter函数,setter和getter是隐藏函数,会取我们写在age后边的函数
*/
let obj = {
    name: 1,
    get age() {
        return 22;
    },
    set age(value) {
        console.log("set");
        obj.name = value;
    },
};
代码实现

ref 使基本数据类型变成响应式,主要是因为存储在 value 属性中,变成了一个引用类型数据

  1. 判断传入的是否是 ref 创建的对象,如果是直接返回;

  2. 传入的不是 ref 创建的对象,内部调用 reactive 创建响应式数据;

  3. 最后创建一个有 value 属性的对象,value属性的值为第二步处理后的值;

    // ... 这里的代码接 reactive 节的代码
    export const ref = (raw) => {
      // 1. 判断 raw 是否是 ref 创建的对象,如果是,直接返回
      if(isObject(raw) && raw.__v_isRef) 
          
      // 2. 传入的是普通数据,进行处理
      // 普通对象 -> 被 reactive 处理为响应式对象   
      // 基本数据 -> 直接返回为基本数据
      let reactiveData = convert(raw)
      
      // 3. 创建一个有 value 属性的对象返回
      const r = {
        __v_isRef: true,		// 注意,这个一定要有
        get value() {
          console.log('ref--get-->>>', reactiveData);
          // todo:收集依赖
          return reactiveData
        },
        set value(newValue) {
          console.log('ref--set-->>>', reactiveData, newValue);
          if(newValue !== reactiveData) {
            raw = newValue
            reactiveData = convert(raw)
            // todo:触发更新
          }
        }
      }
      return r
    }
    

5、reactive vs ref

  • ref 可以把基本数据类型数据,转成响应式对象;

  • ref 返回的对象,重新赋值成对象也是响应式的;

  • reacive 返回的对象,重新赋值丢失响应式;

  • reactive 返回的对象不可以直接解构,需要使用 toRefs ;

6、toRefs

  1. toRefs 函数接收一个 reactive 返回的响应式对象,也就是 Proxy 对象
  2. 把传入的对象的所有属性转成一个类似 ref 返回的对象,注意对 array 和其他数据类型的处理
  3. 把转换后后的对象挂载到一个新的对象上返回
export const toRefs = (proxy) => {
  // 这一步不好理解,对对象和其他数据类型的处理
  let ret = proxy instanceof Array ? new Array(proxy.length) : {}
  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }
  return ret
}

// 将属性转成类似 ref 返回的对象
const toProxyRef = (proxy, key) => {
  let r = {
    __v_isRef: true,
    get value() {
      // 注意:这里不需要数据劫持和更新,因为本来就是响应式的数据了,不需要劫持
      return proxy[key]
    },
    set value(newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

7、computed

computed内部使用的是effect实现(还未完全明白)

export const computed = (getter) => {
  const result = ref()
  effect(() => (result.value = getter()))
  return result
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值