vue2.0,vue3.0原理解读
数据响应式原理机制
1、vue2.0 - Object.defineProperty
原理:利用递归把对象所有属性通过es5 中(Object.defineProperty) 方法绑定get 、set方法。所以数组需要特殊处理数据进行绑定。
1.1 对象拦截方法
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'}
observer(obj);
obj.name = 'webyouxuan';
1.2 数组拦截方法
let oldProtoMehtods = Array.prototype; // 1、首页获取数组的原型属性
//2、将proto 的原型指向Array.prototype
let proto = Object.create(oldProtoMehtods);
// 3、重写数组的这7个方法
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method=>{
Object.defineProperty(proto,method,{
get(){
update();
oldProtoMehtods[method].call(this,...arguments)
}
})
})
function observer(target){
if(typeof target !== 'object'){
return target
}
// 如果不是对象数据类型直接返回即可
if(Array.isArray(target)){
Object.setPrototypeOf(target,proto);
// 给数组中的每一项进行observr
for(let i = 0 ; i < target.length;i++){
observer(target[i])
}
return
};
// 重新定义key
for(let key in target){
defineReactive(target,key,target[key])
}
}
测试
let obj = {hobby:[{name:'youxuan'},'喝']}
observer(obj)
obj.hobby[0].name = 'webyouxuan'; // 更改数组中的对象也会触发试图更新
console.log(obj)
Object.defineProperty缺点
- 无法监听数组的变化
- 需要深度遍历,浪费内存
2、Vue3.0 - Proxy
在学习Vue3.0之前,你必须要先熟练掌握ES6中的 Proxy、Reflect 及 ES6中为我们提供的 Map、Set两种数据结构
先应用再说原理:
let p = Vue.reactive({name:'youxuan'});
Vue.effect(()=>{ // effect方法会立即被触发
console.log(p.name);
})
p.name = 'webyouxuan';; // 修改属性后会再次触发effect方法
源码是采用 ts 编写,为了便于大家理解原理,这里我们采用js来从0编写,之后再看源码就非常的轻松啦!
2.1 reactive方法实现
通过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) ? reactive(res) : res; // 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
}
这里我们将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 = target.hasOwnProperty(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);
这边我测试了一下,没有重复打印 那解决方案就不在这里写了,遇到的朋友可以去下面链接,原文查看,我这边根据下面的文章做了整理。为了更好的理解和学习把!
到这里reactive方法基本实现完毕,接下来就是与Vue2中的逻辑一样实现依赖收集和触发更新
本文参考原文
https://www.cnblogs.com/fs0196/p/12691407.html