阅读 1753

Vue3源码学习(问题总结)

本篇文章会一直更新,直到把vue源码看完,有啥问题就可以在评论中写上一起研究。

小黄鸭调试法

最近发现了一个及其有用的终极调试方法,就是小黄鸭调试法。 小黄鸭调试法: 传说中的大师都是随身携带小黄鸭的,遇到bug就向小黄鸭解释每一行代码的用处,可能讲到一半就灵光乍现,bug灰飞烟灭。
本来只看vue源码的时候感觉遇到了很多问题,把这些问题记录下来,然后带着问题学习源码就感觉更加清晰。

差异

vue2中使用了Object.defineProperty进行数据劫持,对于数组进行单独处理,修改数组原型上的方法

if (Array.isArray) {
  this.observeArray(value)
} else {
  this.walk(value)
}
复制代码

Vue3使用了Proxy进行数据劫持,优异于definePropertyProxy能够对数组修改进行监听。在Vue2中如果使用list[index]=5这种方式是无法监听到的,Proxy解决了这种问题,不仅如此proxy除了提供setget还提供了hasdeleteProperty等等。
Vue2中,对于我们在data中所定义的数据,在Vue初始化的时候,会遍历data中所有的数据进行响应式。

function defineReactive(obj, key, val) {
  /* 对象的子对象递归进行observe并返回子节点的Observer对象  */
  let childOb = observe(val)
}
复制代码

data中绑定的数据足够大的时候,有些数据可能没有用到,就能造成不必要的性能损失。在Vue3中,就避免了这种问题,在Vue初始化的时候只会对data这一层加上proxy,当获取其中数据的时候,才会为对应的数据添加响应式

instance.data = reactive(data);
function reactive(target, handler) {
  observed = new Proxy(target, handler)
}
function createGetter() {
  return function get(target, key) {
    let res = Reflect.get(target, key) 
    return isObject(res)
      ? reactive(res)
      : res;
  }
}
复制代码

例如

data: {
  status: {
    change: true,
  }
}
复制代码

只有当使用status.change的时候,才会创建对应的proxy

proxy的一些问题

多次触发set

arr = []
arrProxy = new Proxy(arr, {
  get: (target, prop) => {
    console.log(prop, 'get');
    return Reflect.get(target, prop)
  },
  set: (target, prop, value) => {
    console.log(prop, 'set')
    Reflect.set(target, prop, value)
    return true
  }
})
arrProxy.push(1)
复制代码

把这段代码在chrome中执行,发现他会触发两次setget操作。把set中的console看成render函数,那么这个操作就会触发多次render,这显然是不合理的。在vue中主要是用下面方式进行解决:

function set(target, key) {
  const hadKey = hasOwn(target, key);
  const oldValue = target[key];
  if (!hadKey) {
    console.log('trigger')
  } else if (val !== oldValue) {
    console.log('trigger')
  }
}
复制代码

可以看到,现在只会执行一次trigger,因为设置length是后执行的,但是这时候数组的长度已经更改了,所以后面的程序不会执行了。

只能监听到第一层

obj = {
  first: {
    inner: 3
  }
}
proxyObj = new Proxy(obj, {
  get: (target, prop) => {
    console.log(prop, 'get');
    return Reflect.get(target, prop)
  },
  set: (target, prop, value) => {
    console.log(prop, 'set')
    Reflect.set(target, prop, value)
    return true
  }
})
proxyObj.first.inner
// first get
proxObj.first.inner = 4
// first get
复制代码

当获取inner属性值或者设置的时候,只会触发到第一层,所以子对象需要我们自己去实现

function get(target, key) {
  const res = Reflect.get(target, key, receiver);
  return isObject(res) ? reactive(res) : res;
}
复制代码

WeakMap

WeakMapes6中新加的一种数据类型,它和Map有何区别

const m = new Map();
m.set('key', 'value')
const n = new WeakMap();
let obj = {};
n.set(obj, 'value')
obj = null;
n.has(obj);
复制代码

Map能够使用字符串来作为key,但是WeakMap只能让对象来作为keyWeakMap最大的优势是能够避免内存泄漏,例如在上面例子中是了,使用了obj作为WeakMapkey,当把obj置为null的时候,这时候WeakMap是无法获取到对应的value值,意思是这个数据已经被垃圾回收器回收掉。

effect的作用

effect.spec.ts

let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7;
expect(dummy).toBe(7)
复制代码

从上面可以看出effect收到一个回调函数,并且立即执行。当触发counterset操作的时候,在执行一遍effect中的函数,下面是effect的实现:

function effect(fn, options) {
  // ... 
  const effect = createReactiveEffect(fn, options);
  if (!options.effect) {
    effect();
  }
  return effect;
}
复制代码

effect就是对回调函数进行一次包装,并在包装的过程中进行一些操作,实际就是依赖收集,最后调用run方法

function run(effect, fn, args) {
  // ...
  try {
    effectStack.push(effect);
    return fn(...args)
  } finally {
    effectStack.pop();
  }
}
复制代码

这里就像vue2中的watcher实现,effectStack是一个全局变量,当发布者data中的数据触发get操作的时候进行依赖收集,当触发set操作的时候,通知所有的订阅者更新。

Log

  1. 12-4 算是大概看完了响应式的部分