This website requires JavaScript.
原创

好奇一研之vue2计算属性原理实现

2020.12.25 17:46 

2 人喜欢
793 次阅读
0 条评论

原本是打算研究下vue3的watchEffect是怎么实现的,捕捉里面的响应式值,某个响应式值发生改变时,则触发console.log输出。。

最简单的思路就是在watchEffecct监听里把console.log这个函数重写,通过arguments判断传进来的值是否为响应式,因为vue3在每个响应式变量上都打上了__v_isReactive的标记,判断是响应式的话,则将此arguments加进这个响应式变量的依赖数组里,而发生改变时,则将这个依赖数组遍历一遍console.log输出就好。。

而此时想着想着,突然想到vue2的计算属性?觉得特神奇。。

为什么函数里嵌着响应式属性,而这个函数就能也跟着响应式呢

核心核心核心核心还是Object.defineProperty

在指定属性get的时候触发defineProperty

在指定属性set的时候也触发defineProperty

它就是这些功能的桥梁

而我们的计算属性声明时的函数,在执行时,如若函数里有响应式数据的操作, 则会触发get/set,而这时候就可以拦截到,进行进一步的操作啦,计算属性莫过于此,油然而生

直接上码

function normalizeInstance(instance) {
  let sourceData = {},
    proxyData = {};
  const computedDepends = {};
  let computedCall
  function normalizeData(data) {
    sourceData = JSON.parse(JSON.stringify(data));
    for (let key in data) {
      Object.defineProperty(proxyData, key, {
        get() {
          // (computedDepends依赖数组点)
          if (computedCall) {
            (computedDepends[key] ??= []).push(computedCall);
          }
          return sourceData[key];
        },
        set(newVal) {
          sourceData[key] = newVal;
          // (computedDepends依赖数组点)
          // 当重新赋值,发生改变时,则更新一遍所有依赖
          computedDepends[key]?.forEach((fun) => fun());
        },
      });
    }
  }
  function normalizeComputed(computed) {
    for (let key in computed) {
      const fun = computed[key];
      if (typeof fun === "function") {
        let val;
        /* 
          初始化下val,会触发fun()方法,而fun()方法可能会触发data数据的get
          例(计算count点)中的this.num1就触发了data数据的get,key则为num1
          而触发时,则可以将更新通知(computedCall赋值点)加入num1的依赖数组(computedDepends依赖数组点)里
          若不触发,那肯定嘛事不干啦
         */
        /* (computedCall赋值点) */
        (computedCall = function () {
      // 改变fun内部的this指向,让其使用this指向操作时可以拦截到
          val = fun.call(proxyData); 
        })();
        Object.defineProperty(proxyData, key, {
          get() {
            return val;
          },
        });
      } else {
        throw new Error("computed不允许传入普通值哦亲,只允函数哦亲");
      }
    }
    computedCall = null
  }
  // 先处理data的先,后面处理计算属性时,会用到核心的data
  normalizeData(instance.data());
  normalizeComputed(instance.computed);
  return proxyData;
}
var instance = {
  data() {
    return {
      num1: 1,
      num2: 19,
    };
  },
  computed: {
    /* 计算count点 */
    count() {
      console.log("是的,发生改变了");
      return this.num1 * this.num2;
    },
    test() {
      return "我就单纯返回一个东西,不拿data的数据作计算";
    },
  },
};
var data = normalizeInstance(instance);

data.count // 19

data.num1 = 3

data.count // 57

data.num2 = 10

data.count // 30

data.test // "我就单纯返回一个东西,不拿data的数据作计算"

// 默不作声的test里并没有掺杂响应式数据,所以也没有触发data的拦截,自然是一个初始化后就诈尸的死函数了…哈哈

接下来就到watchEffect了…它是如何做到数据发生改变时,能按需console输出的,下篇见...

  • 😃
  • 😂
  • 😅
  • 😉
  • 😌
  • 😔
  • 😓
  • 😘
  • 😡
  • 😭
  • 😱
  • 😳
  • 😵
  • 🌚
  • 👍
  • 👎
  • 💪
  • 🌹
  • 💊
  • 🇨🇳
  • 🇺🇸