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方法基本实现完毕
来源:oschina
链接:https://my.oschina.net/u/560237/blog/4883640