对于做vue的响应式,问自己的内心的几个问题如下:
1)怎么做的数据劫持,vue2和 vue3的区别?
答: vue2的数据劫持是使用是es5的 Object.defineProperty的setter 和
getter方法来对对于数据的读取和设置的, 但是这里有一个最大的缺陷是 需要遍历对象的每一个属性进行setter 和 getter,
对于大数据量,或者说是复杂的组件不友好,还有就是对象里面新增属性或者说是删除对象属性做不了数据劫持,所以vue2.0提出了 vue.s e t 和 v u e . set 和 vue.set和vue.delete ; vue3的数据劫持使用的是 Proxy(代理) 来实现的,可以对大数据量的setter 和 getter,
由于每个对象的getter 返回的都是一个代理,
这使得代理可以缓存,使得大数据也是可以的,缺点是ie中的浏览器是没有es6的proxy(IE浏览器得es6转换成es5得问题),vue3是放弃对ie的支持了
创建的项目的
2)简述vue2和vue3分别是如何实现响应式的?vue3在响应式上的提升在哪里?
vue2的响应式是使用Object.defineProperty完成的,它会对原始对象有侵入。
在创建响应式阶段,会递归遍历原始对象的所有属性,当对象属性较多、较深时,对效率的影响颇为严重。不仅如此,由于遍历属性仅在最开始完成,因此在这儿之后无法响应属性的新增和删除。
在收集依赖时,vue2采取的是构造函数的办法,构造函数是一个整体,不利于tree shaking。
vue3的响应式是使用Proxy完成的,它不会侵入原始对象,而是返回一个代理对象,通过操作代理对象完成响应式。
由于使用了代理对象,因此并不需要遍历原始对象的属性,只需在读取属性时动态的决定要不要继续返回一个代理,这种按需加载的模式可以完全无视对象属性的数量和深度,达到更高的执行效率。
由于ES6的Proxy可以代理更加底层的操作,因此对属性的新增、删除都可以完美响应。
在收集依赖时,vue3采取的是普通函数的做法,利用高效率的WeakMap实现依赖记录,这利于tree shaking,从而降低打包体积。
思路:如下:
vue2响应系统
vue2响应系统由两个核心部件组成:
- 数据响应部件:该部件的作用是将一个对象的所有属性转换为getter和setter,当读取属性或设置属性时,可以发出通知
- 依赖收集部件:该部件的作用是在一个函数的执行过程中,记录该函数所依赖的顶层函数,将来一旦发出一个通知,将重新执行该顶层函数
数据响应式实现
依赖收集实现
vue3响应系统
vue3中,将响应系统彻底的分离了出去成为了一个单独的库@vue/reactivity
在响应式系统中,为了更好的支持Tree Shaking,因此将所有的功能都进行了函数化,并且全部使用TypeSript重写
数据响应实现
依赖收集
代码如下:
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 来源:渡一教育