简单聊聊,vue3响应式的基础原理

196 阅读4分钟

前言

书接上文,接下来让我来聊聊vue3响应式的基础原理吧。

vue3响应式

同样的一段简单地代码

let obj = {
  a: 1,
  b: 2
}

console.log(obj.a);
console.log(obj.b);

image.png

接下来我们使用代理的方式将obj代理后再次访问会怎么样呢?

let obj = {
  a: 1,
  b: 2
}

let proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log('读取');
    return target[key]
  },
  set(target, key, receiver) {
    console.log('修改');
  }
})

console.log(obj.a);
console.log(obj.b);
console.log(proxy.a);
console.log(proxy.b);

如上代码所示,使用了proxyobj进行了代理,值得一提的是代理操作时完全在另一个对象上进行,而不是像vue2当中的数据劫持对原有的对象进行操作。如下打印中obj.aobj.b依然能够正常地访问不受限制,但想要数据变成响应式则必须使用proxyproxy提供了十三种方法对数据进行不同的操作,不像数据劫持只是提供了getset两种方式,proxy提供了比如(get、set、has、deleteProperty)等十三种方法。getset分别都接受三个参数target, key, receiver,target指元对象、key指的访问的对象的属性、而receiver可以帮助我们区分不同的调用上下文,同理set也一样。

image.png

当对象当中嵌套对象时,proxy会如何呢?

function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  return createReactiveObject(target)
}
function createReactiveObject(target) {
  //十三种方法
  let baseHandler = {
    get(target, key, receiver) {
      console.log('读取');
      let result = Reflect.get(target, key)
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, receiver) {
      console.log('修改');
      return Reflect.set(target, key, value, receiver)
    }
  }
  //进行代理
  let observer = new Proxy(target, baseHandler)
  return observer
}
let obj = {
  a: 1,
  b: 2,
  c: {
    x: 100
  }
}
let proxy = reactive(obj)
console.log(proxy.c.x);

image.png

当对象被递归代理后,原本vue2中需要额外增添方式实现数组方法,以及无法解决的数组长度问题全部都得到了解决。

function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  return createReactiveObject(target)
}
function createReactiveObject(target) {
  //十三种方法
  let baseHandler = {
    get(target, key, receiver) {
      console.log('读取');
      let result = Reflect.get(target, key)
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      console.log('修改');
      return Reflect.set(target, key, value, receiver)
    }
  }
  //进行代理
  let observer = new Proxy(target, baseHandler)
  return observer
}
let obj = {
  a: 1,
  b: 2,
  c: {
    x: 100
  },
  d: [1, 2, 3]
}
let proxy = reactive(obj)
// console.log(proxy.c.x);
proxy.d.push(3)
proxy.d.length = 100

值得注意的是在本段代码中是将递归写在了get方法体当中的,这样的好处是只有当主动地访问对象中的属性才会对对象中的属性进行代理,如果没有被get访问则不会进行代理进一步提高了代码的性能。并且proxy的特性不同于数据劫持可以对对象中的方法(push,shift,pop等)进行代理。

image.png

接下来又会引申出另外一个问题,当一个对象被代理后,继续使用另一个对象再次代理已经被代理过的对象。

let toProxy = new WeakMap()// 原对象:代理对象

function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  return createReactiveObject(target)
}
function createReactiveObject(target) {
  // 想被代理的一定要是一个对象
  if (!isObject(target)) {
    return target
  }
  let proxy = toProxy.get(target);
  if (proxy) {
    return proxy;
  }
  //十三种方法
  let baseHandler = {
    get(target, key, receiver) {
      console.log('读取');
      let result = Reflect.get(target, key)
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      console.log('修改');
      return Reflect.set(target, key, value, receiver)
    }
  }
  //进行代理
  let observer = new Proxy(target, baseHandler)
  // 将代理对象存入map当中
  toProxy.set(target, observer);
  return observer
}
let obj = {
  a: 1,
  b: 2,
  c: {
    x: 100
  },
  d: [1, 2, 3]
}
let proxy = reactive(obj)
let proxy1 = reactive(obj)

由于需要主动判断一个对象是否被代理过,此时需要有一个数据结构能够存入对象作为键,毫无疑问选择map数据类型。使用了弱引用类型WeakMap,最直接的效果便是当用不到时会主动销毁降低了内存的损耗,提升了代码的性能。在每次进行代理之前判断是否存在于map中,达到无法代理已经被代理的对象的效果。

还省下最后一个问题,当一个对象被代理后,接着再次对这个代理对象进行二次代理怎么办?

let toProxy = new WeakMap()// 原对象:代理对象
let toRaw = new WeakSet();//代理对象
function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  return createReactiveObject(target)
}
function createReactiveObject(target) {
  // 想被代理的一定要是一个对象
  if (!isObject(target)) {
    return target
  }
  let proxy = toProxy.get(target);
  if (proxy) {
    return proxy;
  }
  if (toRaw.has(target)) {
    return target;
  }
  //十三种方法
  let baseHandler = {
    get(target, key, receiver) {
      console.log('读取');
      let result = Reflect.get(target, key)
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      console.log('修改');
      return Reflect.set(target, key, value, receiver)
    }
  }
  //进行代理
  let observer = new Proxy(target, baseHandler)
  // 将代理对象存入map当中
  toProxy.set(target, observer);
  toRaw.add(observer, target);
  return observer
}
let obj = {
  a: 1,
  b: 2,
  c: {
    x: 100
  },
  d: [1, 2, 3]
}
let proxy = reactive(obj);
let proxy1 = reactive(obj);
let proxy2 = reactive(proxy);

console.log(proxy === proxy1);
console.log(proxy === proxy2);

需要判断一个对象是否被代理,使得他无法被二次代理,需要一个无法重复的数组存入,每次进行代理操作的时候进行查找判断,set数据结构刚好适用于该场景。同样和上一步的理由一样提升代码的性能选择采用WeakSet

image.png