vue2.0 vue3.0 响应机制对比

房东的猫 提交于 2021-01-07 18:50:01

vue2.0 响应式原理机制:defineProperty.  原理就是拦截对象,给对象增加get和Set方法,因为核心是defineProperty, 所以还需要对数组的方法进行拦截

对对象进行拦截

function observer(target){
 //如果不是对象数据类型直接返回即可
  if(typeof target !== 'object'){
   return target
  }

  //重新定义key
  for(let key in target){
   defineReactive(target,key,target[key])
  }
}

function update(){
  console.log('update view')
}

function defineReactive(obj,key,value){
  observer(value)  //有可能 对象是多层, 递归劫持

  Object.defineProperty(obj,key,{
    get(){
      //在get方法中收集依赖
      return value
    },
    set(newVal){
      if(newVal !== value){
        observer(value);
        update(); //在set方法中触发更新
      }
    }
  })
}

let obj = {name:'youxuan'}
observe(obj);
obj.name = 'webyouxuan'

对数组方法劫持:

let oldProtoMethods = Array.prototype;
let proto = Object.create(oldProtoMethods);

['push',;'pop','shift','unshift'].forEach(method=>{
  Object.defineProperty(proto,method,{
   get(){
      update();
      oldProtoMethods[method].call(this,...arguments)
    }
  })
})

function observer(target){
  if(typeof target !=='object'){
    return target
  }

  if(Array.isArray(target)){
    Object.setPrototypeOf(target,proto);

    //给数组中的每一项进行observer
    for(let i=0; i<target.length;i++){
       observer(target[i])
    }
    return
  }

  //重新定义key
  for(let key in target){
    defineReactive(target,key,target[key])
  }
}

Object.defineProperty缺点

  • 无法监听数组的变化
  • 需要深度遍历,浪费内存

 

vue3的响应机制

  通过proxy 自定义获取、增加、删除等行为

function reactive(target){
    // 创建响应式对象
    return createReactiveObject(target);
}
function isObject(target){
    return typeof target === 'object' && target!== null;
}
function createReactiveObject(target){
    // 判断target是不是对象,不是对象不必继续
    if(!isObject(target)){
        return target;
    }
    const handlers = {
        get(target,key,receiver){ // 取值
            console.log('获取')
            let res = Reflect.get(target,key,receiver);
            return res;
        },
        set(target,key,value,receiver){ // 更改 、 新增属性
            console.log('设置')
            let result = Reflect.set(target,key,value,receiver);
            return result;
        },
        deleteProperty(target,key){ // 删除属性
            console.log('删除')
            const result = Reflect.deleteProperty(target,key);
            return result;
        }
    }
    // 开始代理
    observed = new Proxy(target,handlers);
    return observed;
}
let p = reactive({name:'youxuan'});
console.log(p.name); // 获取
p.name = 'webyouxuan'; // 设置
delete p.name; // 删除

我们继续考虑多层对象如何实现代理

let p = reactive({ name: "youxuan", age: { num: 10 } });
p.age.num = 11
由于我们只代理了第一层对象,所以对 age对象进行更改是不会触发set方法的,但是却触发了 get方法,这是由于   p.age会造成   get操作
get(target, key, receiver) {
      // 取值
    console.log("获取");
    let res = Reflect.get(target, key, receiver);
    return isObject(res) // 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
    ? reactive(res) : res;
}
这里我们将 p.age取到的对象再次进行代理,这样在去更改值即可触发 set方法

我们继续考虑数组问题
我们可以发现Proxy默认可以支持数组,包括数组的长度变化以及索引值的变化

let p = reactive([1,2,3,4]);
p.push(5);
但是这样会触发两次 set方法,第一次更新的是数组中的第 4项,第二次更新的是数组的 length

我们来屏蔽掉多次触发,更新操作

set(target, key, value, receiver) {
    // 更改、新增属性
    let oldValue = target[key]; // 获取上次的值
    let hadKey = hasOwn(target,key); // 看这个属性是否存在
    let result = Reflect.set(target, key, value, receiver);
    if(!hadKey){ // 新增属性
        console.log('更新 添加')
    }else if(oldValue !== value){ // 修改存在的属性
        console.log('更新 修改')
    }
    // 当调用push 方法第一次修改时数组长度已经发生变化
    // 如果这次的值和上次的值一样则不触发更新
    return result;
}

解决重复使用reactive情况

// 情况1.多次代理同一个对象
let arr = [1,2,3,4];
let p = reactive(arr);
reactive(arr);

// 情况2.将代理后的结果继续代理
let p = reactive([1,2,3,4]);
reactive(p);
通过 hash表的方式来解决重复代理的情况
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象
function reactive(target) {
  // 创建响应式对象
  return createReactiveObject(target);
}
function isObject(target) {
  return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
  return target.hasOwnProperty(key);
}
function createReactiveObject(target) {
  if (!isObject(target)) {
    return target;
  }
  let observed = toProxy.get(target);
  if(observed){ // 判断是否被代理过
    return observed;
  }
  if(toRaw.has(target)){ // 判断是否要重复代理
    return target;
  }
  const handlers = {
    get(target, key, receiver) {
      // 取值
      console.log("获取");
      let res = Reflect.get(target, key, receiver);
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      let oldValue = target[key];
      let hadKey = hasOwn(target,key);
      let result = Reflect.set(target, key, value, receiver);
      if(!hadKey){
        console.log('更新 添加')
      }else if(oldValue !== value){
        console.log('更新 修改')
      }
      return result;
    },
    deleteProperty(target, key) {
      console.log("删除");
      const result = Reflect.deleteProperty(target, key);
      return result;
    }
  };
  // 开始代理
  observed = new Proxy(target, handlers);
  toProxy.set(target,observed);
  toRaw.set(observed,target); // 做映射表
  return observed;
}
到这里 reactive方法基本实现完毕

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!