vue2 和 vue3 响应式 代码实现原理
对于做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.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响应系统由两个核心部件组成:
- 数据响应部件:该部件的作用是将一个对象的所有属性转换为
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
来源:渡一教育
上一篇: Bootstrap 第14章 下拉菜单和滚动监听插件
下一篇: React 滚动条应用实例