欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue2 和 vue3 响应式 代码实现原理

程序员文章站 2024-02-02 11:21:46
...

对于做vue的响应式,问自己的内心的几个问题如下:

1)怎么做的数据劫持,vue2和 vue3的区别?

答: vue2的数据劫持是使用是es5的 Object.defineProperty的setter 和
getter方法来对对于数据的读取和设置的, 但是这里有一个最大的缺陷是 需要遍历对象的每一个属性进行setter 和 getter,
对于大数据量,或者说是复杂的组件不友好,还有就是对象里面新增属性或者说是删除对象属性做不了数据劫持,所以vue2.0提出了 vue. s e t 和 v u e . set 和 vue. setvue.delete ; vue3的数据劫持使用的是 Proxy(代理) 来实现的,可以对大数据量的setter 和 getter,
由于每个对象的getter 返回的都是一个代理,
这使得代理可以缓存,使得大数据也是可以的,缺点是ie中的浏览器是没有es6的proxy(IE浏览器得es6转换成es5得问题),vue3.0对ie的低版本不怎么友好,目前ie11是可以打开vue3.0
创建的项目的

2)简述vue2和vue3分别是如何实现响应式的?vue3在响应式上的提升在哪里?

vue2的响应式是使用Object.defineProperty完成的,它会对原始对象有侵入。
在创建响应式阶段,会递归遍历原始对象的所有属性,当对象属性较多、较深时,对效率的影响颇为严重。不仅如此,由于遍历属性仅在最开始完成,因此在这儿之后无法响应属性的新增和删除。
在收集依赖时,vue2采取的是构造函数的办法,构造函数是一个整体,不利于tree shaking。

vue3的响应式是使用Proxy完成的,它不会侵入原始对象,而是返回一个代理对象,通过操作代理对象完成响应式。
由于使用了代理对象,因此并不需要遍历原始对象的属性,只需在读取属性时动态的决定要不要继续返回一个代理,这种按需加载的模式可以完全无视对象属性的数量和深度,达到更高的执行效率。
由于ES6的Proxy可以代理更加底层的操作,因此对属性的新增、删除都可以完美响应。
在收集依赖时,vue3采取的是普通函数的做法,利用高效率的WeakMap实现依赖记录,这利于tree shaking,从而降低打包体积。

思路:如下:

vue2响应系统

vue2响应系统由两个核心部件组成:

  • 数据响应部件:该部件的作用是将一个对象的所有属性转换为gettersetter,当读取属性或设置属性时,可以发出通知
  • 依赖收集部件:该部件的作用是在一个函数的执行过程中,记录该函数所依赖的顶层函数,将来一旦发出一个通知,将重新执行该顶层函数

数据响应式实现

vue2 和 vue3 响应式 代码实现原理

依赖收集实现

vue2 和 vue3 响应式 代码实现原理

vue3响应系统

vue3中,将响应系统彻底的分离了出去成为了一个单独的库@vue/reactivity

在响应式系统中,为了更好的支持Tree Shaking,因此将所有的功能都进行了函数化,并且全部使用TypeSript重写

数据响应实现

vue2 和 vue3 响应式 代码实现原理

依赖收集

vue2 和 vue3 响应式 代码实现原理

代码如下:

vue2

// 判断传入的是否为对象
const isObj = obj => typeof obj === 'object' && obj !== null && !Array.isArray(obj);

// 一个订阅构造函数
function Dep() {
  // 用一个set或者数据来记录依赖关系
  this.recordsSets = new Set();
}
// 收集依赖
Dep.prototype.denpend = function () {
  if (currentExcuseFunc) {
    this.recordsSets.add(currentExcuseFunc);
  }
}
// 通知依赖执行
Dep.prototype.notify = function () {
  // 执行依赖
  this.recordsSets.forEach(fn => fn());
}

let currentExcuseFunc = null;
// 自动执行, 用于执行依赖关系
function autorun(fn) {
  // // 获取当前执行的函数
  // currentExcuseFunc = fn;
  // // 执行函数
  // fn();
  // currentExcuseFunc = null;

  // 为了 动态收集依赖,需要从新运行
  function funcWrapper(){
    currentExcuseFunc = funcWrapper;
    fn();
    currentExcuseFunc = null;
  }
  funcWrapper();
}

// 一个观察者模式
function observe(obj) {
  if (!isObj(obj)) {
    return;
  }
  // 遍历obj的多个数据
  Object.keys(obj).forEach(key => {
    let originValue = obj[key];
    observe(originValue) // 继续保持为响应式
    let dep = new Dep();
    Object.defineProperty(obj, key, {
      get() {
        // 收集依赖
        dep.denpend();
        return originValue;
      },
      set(val) {
        observe(val)
        originValue = val;
        // 发出通知去执行
        dep.notify();
      }
    })
  })
}


let obj = {
  name: 'cll',
  age: 23,
  sex: 'male',
  addr: {
    counter: 'China',
    city: 'JX'
  }
}

observe(obj);

autorun(() => {
  // console.log(obj.name, obj.addr.city, obj.addr.counter,'--------');
  if(obj.age % 2 === 0){
    console.log(obj.name);
  }else{
    console.log(obj.sex);
  }
})





vue3

// vue3的响应式原理

// 对象使用一个map来接受, key为obj, value为map, map 里面的key 为属性值, value是收集的依赖,
const targetMap = new WeakMap();
// 当前执行的函数
let currentExcuseFunc = null;
// 判断传入的是否为对象
const isObj = obj => typeof obj === 'object' && obj !== null && !Array.isArray(obj);

// 用于缓存对象的属性,如果存在直接返回,不存在直接返回一个代理,里面的结构式 key:obj, value: proxyobj
const cacheMapper = new WeakMap();

// 收集依赖,需要传入对象,和对应的属性

/**
 * 记录当前正在执行的函数,依赖哪个对象的哪个属性
 * 相当于 dep.depend
 * @param {*} target
 * @param {*} key
 */
function track(target, key) {
  // 从map中获取对象
  let objMap = targetMap.get(target);
  if (!objMap) {
    objMap = new Map();
    // 把第二层的map放入weakmap中
    targetMap.set(target, objMap);
  }
  let valueSets = objMap.get(key);
  if (!valueSets) {
    // 建立一个set来存放依赖
    valueSets = new Set();
    objMap.set(key, valueSets);
  }
  if (currentExcuseFunc) {
    valueSets.add(currentExcuseFunc);
  }
}

// 发出通知,执行依赖,需要传入对象和对应的属性
/**
 * 依次触发依赖该对象该属性的所有函数
 * 相当于 dep.notify
 * @param {*} target
 * @param {*} key
 */
function trigger(target, key) {
  // 从map中获取对象
  let objMap = targetMap.get(target);
  if (!objMap) {
    return;
  }
  let valueSets = objMap.get(key);
  if (!valueSets) {
    return;
  }
  // 通知所有相关的依赖,执行
  valueSets.forEach(fn => fn())
}

// 自动执行,用于去看哪些依赖为引用
function effect(fn) {
  function funcWrapper() {
    currentExcuseFunc = funcWrapper;
    fn();
    currentExcuseFunc = null;
  }
  funcWrapper();
}


// 观察者模式
function reviewer(obj) {
  // 通过代理来做
  if (!isObj(obj)) {
    return obj;
  }

  // 获取一个代理对象
  let getObj = cacheMapper.get(obj)
  if (getObj) {
    // 如果存在直接返回
    return getObj;
  }
  // 不存在new 一个代理
  getObj = new Proxy(obj, {
    get(target, key) {
      // 收集依赖
      track(target, key);
      // 返回的也需要一个代理, 不会无法遍历对象里面的对象
      return reviewer(Reflect.get(target, key));
    },
    set(target, key, value) {
      // 设置的值,也需要是一个代理
      Reflect.set(target, key, reviewer(value));
      trigger(target, key); // 发出通知,执行依赖
    },
    // 删除一个属性
    deleteProperty(target, key){
      Reflect.deleteProperty(target, key);
      trigger(target, key);
    }
  })
  cacheMapper.set(obj, getObj);
  return getObj;
}



let obj = reviewer({
  name: 'cll',
  age: 23,
  addr: {
    counter: 'China',
    city: 'JX'
  }
})


effect(() => {
  console.log(obj.name, obj.addr.city, obj.addr.counter, '---')
})

obj.name = 'chen' // --> chen JX China ---
obj.addr.city = '中国' // --> chen 中国 China ---
obj.sex = 'male' // 不会有打印```


源码地址: https://gitee.com/Cll12345/vue2-and-vue3.git
来源:渡一教育
相关标签: 原理 vue前端