Vue2和Vue3 数据响应式原理

Vue 数据响应式原理

面试题

  • 响应式原理
  • 为什么vue3的proxy会比vue2的Object.defineProperty要快
  • vue2和vue3响应式的区别
  • Object.defineProperty 与 Proxy 的区别、优缺点
  • Proxy可以拦截哪些方法
  • 为什么proxy要结合reflect进行修改
  • Reflect是什么, 都有哪些静态方法
  • 观察者模式和发布订阅模式的区别
  • 手写发布订阅机制

为什么vue3响应式优于vue2响应式

  • Vue2的响应式是基于Object.defineProperty实现的
  • Vue3的响应式是基于ES6的Proxy+Reflect来实现的

vue2中存在的问题

  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
  • 对象的新增属性、删除属性、界面不会更新
    vue2的解决方法: 通过vm.$set(obj,key.val)/vm.$delete(obj,key)新增属性/删除属性
  • 直接通过下标修改数组,界面不会自动更新
    vue2的解决方法:vm.$set(arr,index.value),调用数组的splice方法

vue3中的优化
vue2是监听对象的属性,vue3是监听对象

  • 不需要一次性遍历data的属性,可以显著提高性能。需要的时候再进行深度监听。
  • 基于Proxy,对被代理对象设置拦截,访问或操作被代理对象都要先通过拦截。所以可以监听对象属性的添加和删除。
  • 可以原生监听数组,不需要通过重写方法来实现对数组的监控。

关于惰性深度监听的笔记:https://juejin.cn/post/6979368550225936392#heading-4

Object.defineProperty()和Proxy构造函数

Proxy可以创建一个代理对象,实现对其他对象的代理。外界对被代理对象进行的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy是一个构造函数,第一个参数是被代理对象,第二个参数是一个对象,里面存放拦截的操作

const p = new Proxy(obj,{
	get(){},
	set(){}
})

Proxy可以拦截哪些方法?
proxy只能拦截一个对象的基本操作

  • 读取操作,访问属性比如proxy.fooproxy['foo'] : get(被代理对象, 读取的属性, [简单理解为this,谁在读取属性])
  • 读取操作,判断对象或原型上是否存在给定的key,比如key in obj :has(被代理对象, 判断的属性)
  • 读取操作,ownKeys(被代理对象)方法用来拦截对象自身属性的读取操作。
    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • for...in循环
  • 设置操作,设置属性的值proxy.foo = vproxy['foo'] = v: get(被代理对象, 设置的属性, [简单理解为this,谁在设置属性])
  • 调用操作 、call和apply操作: apply(被代理对象,被代理对象的上下文(this),目标对象的参数数组)
  • …(具体13种方法)

Reflect有哪些静态方法
ProxyReflect的方法都是一一对应的,代码会更易读。

常用方法

  • Reflect.get(): 获取对象身上某个属性的值,类似于 target[name]。
  • Reflect.set(): 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
  • Reflect.has(): 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
  • Reflect.deleteProperty(): 作为函数的delete操作符,相当于执行 delete target[name]。
为什么要使用Reflect

1.修改某些 Object 方法的返回结果,让语义变的更加规范化
比如使用Proxy代理对象obj,当通过代理对象修改obj上的不可修改属性时,会抛出错误阻塞后面的代码。使用Reflect.set返回false,代码正常执行

//1.假设对象中的name属性不允许修改
const obj = {}
Object.defineProperty(obj,"name",{
    value:"name",
    writable: false
})
//2.通过Proxy代理该对象之后,对obj的name属性就行修改会抛出错误,阻塞后面代码的执行
const proxy = new Proxy(obj, {
    get(target, key,) {
        // 注意,这里我们没有使用 Reflect 来进行读取
        return target[key]
    },
    set(target, key, value) {
        // 注意,这里同样没有使用 Reflect 来进行设置
        return target[key] = value
    }
})
proxy.name = '王五'
console.log('阻塞了')
  1. Reflect 中的 receiver 参数可以理解为函数调用过程中的this代表谁读取了属性 可以解决 访问器属性中的 this 的指向导致无法触发响应式的问题
    Reflect.get(target, key, receiver) 类似于 Reflect.get(target, key).call(receiver) 改变 this 的指向。
    访问器属性中的 this 的指向问题
const obj = {
    name: 'raman',
    get value() {
        console.log('value 中的 this:', this, this === obj)
        return this.name
    }
}
obj.value; //name

const proxy = new Proxy(obj, {
    get(target, key) {
        return target[key];
    }   
})
effect(()=>{console.log(proxy.value));
//通过代理proxy.value访问时,先触发get返回obj.value,触发访问器,此时访问器中this指向的obj
/*
这里的this指向obj,obj对象是一个原始数据,并不是响应式对象,所以将无法和副作用函数建立联系。
effect(()=>{console.log(obj.value));
*/

Vue2 响应式原理

对象的响应式原理

完整流程图

在这里插入图片描述
编译模板–编译模板过程
绑定响应式: Object.defineProperty + 观察者模式
数据改变时更新视图–触发diff-patch过程

过程详述

总结

  1. 循环给一个数据内的所有属性(包括子属性)通过 Object.defineProperty函数都转换成getter/setter形式。
  2. 为每个属性绑定一个dep对象,dep用来收集数据的依赖也就是watcher对象。在get的时候,将依赖存进dep中,在set数据改变的时候,通知dep中的依赖数据发生改变了。
  3. 所以在watcher对象中观察数据的变化,当数据变化时通过dep通知(notify)watcher数据改变,触发watcher对象的更新函数->更新视图-触发diff-patch过程。

问题1:数据被修改后,Vue内部如何监听message数据的改变的? --> Object.defineProperty -> 监听对象属性的改变
问题2:当数据发生改变,Vue如何知道需要通知哪些地方更新界面? -->观察者模式

  1. Object.defineProperty -> 监听对象属性的改变
    defineReactive函数定义一个响应式数据
function defineReactive(data,key,val){
	Objcet.defineProperty(data,key,{//代码①
		enumerable:true,
		configurable:true,
		get:function(){
			return val;
		},
		set:function(){
			if(val===newVal)return;
			val = newVal;
		}
	})
}
  1. 定义Observer类将一个正常的object转换成被侦测的object,循环给一个数据内的所有属性(包括子属性)都转换成getter/setter形式。
//{a:"x",b:{c:"y",d:"z"}}
//Observer类的作用将正常的object转换为被侦测的object
class Observer{
	constructor(data){
		this.data = data;
		if(!Array.isArray(data)){ //这里数组和对象都会进来,只处理对象
			Object.keys(data).forEach(key=>{
			defineReactive(this.data,key,data[key]);
			})
		}	
	}
}
function defineReactive(data,key,val){
	//如果val是对象,比如b:{c:"y",d:"z"},说明不是最里层,还需要对{c:"y",d:"z"}进行转换getter/setter
	if(typeof val === 'object')new Observer(val);//递归的目的是每一层的属性都应该被绑定响应式
	//..代码①
}
  1. 现在对数据绑定了监听,但是当数据发生变化时,我们通知谁?我们怎么知道那些地方使用了这个数据?
    对于模板来说使用了name数据,也就是调用了name.get()方法,所以我们可以在get方法中存储用到了name属性的地方,叫做name属性的依赖。
<template>
	{{name}}
</template>

定义Dep类,Dep类的目的是对依赖进行管理,比如存储依赖,删除依赖、给依赖发更新通知等等,那么Dep类与某数据的关系应该是一一对应,所以我们在绑定响应式时,可以为每个属性绑定一个dep对象。在get的时候,将使用了name的地方(依赖)存进dep中,在set数据改变的时候,通知dep中的依赖数据发生改变了,Object在getter中收集依赖,在stter中触发依赖

class Dep {//Dep类存储依赖,添加依赖, 删除依赖,通知依赖等
	constructor(){
		this.subs = [];//subs存储依赖
	}
	addSub(sub){
		this.subs.push(sub);
	}
	depend(){
		//if(依赖){
		//	this.addSub(依赖);
		//}
		if(window.target){
			this.addSub(window.target);
		}
	}
	notify(){
		this.subs.forEach(sub =>{
			sub.update(); //通知这个属性的所有依赖数据更新
		})
	}
}
function defineReactive(data,key,val){
	if(typeof val === 'object')new Observer(val);
	let dep = new Dep(); //绑定dep对象
	Objcet.defineProperty(data,key,{
		enumerable:true,
		configurable:true,
		get:function(){
			dep.depend();//修改dep对象,这里应该添加依赖
			return val;
		},
		set:function(){
			if(val===newVal)return;
			val = newVal;
			dep.notify();//通知依赖,数据发生了修改
		}
	})
}
  1. 依赖是什么?之前我们把使用了数据的地方,叫做依赖,那么依赖描述成数据结构应该是什么样子的?
    定义一个Watcher类,一个Watcher对象就是一个依赖
    当属性变化时,就会调用dep的notify属性循环通知watcher调用update()方法,修改模板中的数据。
class Watcher{
	constructor(vm,name,node){
		this.node = node;
		this.vm = vm;
		this.name = name;
		window.target = this, //我们给依赖命名为window.target = watcher对象,所以依赖就是watcher对象
		this.update();
		window.target = null; //并没有绑定在实例上,全局仅有一个,数据更新后会重新调用get,防止一个watcher对象被多次加入dep.sub数组中
	}
	update(){//将{{name}}的name更新为vm里面的值,这里是在vm上代理了_data的值
		this.node.nodeValue = this.vm[this.name]; 
		//从vm中取某个属性,相当于调用该属性的getter方法,此时window.target是有值的,值为watcher对象,所以这个watcher对象会被存储进dep.sub数组里面
	}
}
//watcher对象在解析模板中的指令的时候会被创建new Watcher

Vue2 数组响应式原理

重写了数组的七个方法,这7个方法都会改变原数组

  • push:向数组的结尾添加一个或多个元素
  • pop:从数组的结尾删除一个元素
  • unshift:向数组的开头添加一个或多个元素
  • shift:删除数组的第一个元素
  • splice:删除数组中的指定元素,并为数组添加新元素
  • sort:数组元素进行排序
  • reverse:反转数组

思路

  1. 定义一个拦截器对象,拦截器对象的__proto__隐式原型指向Array.prototype拦截器对象重写上述的7个方法,重写的目的是增加响应式,但是最终调用的函数原型上的方法
  2. 为数组增加响应式的办法:让数组的__proto__隐式原型指向拦截器对象的显式原型
  3. 数组是在getter中收集依赖,在拦截器中触发依赖。数组中元素的修改是通过拦截器重写的方法,那么如果触发了重写方法说明数据改变了,此时我们就需要通知依赖,所以是在拦截器中触发依赖。
    在这里插入图片描述
过程详述
  1. 我们需要创建拦截器对象,重写这7个方法,重写的目的是增加响应式,但是最终调用的函数原型上的方法
    比如调用push方法时,实际调用的时arrayMethods.push,也就是mutator函数,最终调用的是原型上的push方法 --> 加一层mutator的目的是可以在mutator函数做一些其他事情,比如通知依赖
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto); //arrayMethods.__proto__ = Array.prototype

const methodsNeedChange = ['push','pop','shift','unshift','splice','sort','reverse']; //需要被改写的七个方法
methodsNeedChange.forEach(function(method){
	const original= arrayProto[method]; //缓存原来的方法,最终还是会被调用
	Object.defineProperty(arrayMethods, method, {//为拦截器对象增加7个方法
		//value:重写的方法
		value:function mutator(...args){
			return original.apply(this,args); //最终调用的是原型的方法,this指向拦截器
		}
		enumerable:false;//不可以被枚举
		writable:true; //可以被遍历
		configurable:true; //可以被删除
	})
	 
}
  1. 我们现在要为数组增加响应式,方法是让数组的__proto__隐式原型指向拦截器对象,Observer类的作用就是增加响应式。
//引入arrayMethods
class Observer{
	constructor(data){
		this.data = data;
		if(Array.isArray(data)){//为数组增加响应式
			data.__proto__ = arrayMethods;
		}
		else{ //为对象增加响应式
			Object.keys(data).forEach(key=>{
			defineReactive(this.data,key,data[key]);
			})
		}	
	}
}

注意:有些浏览器不支持__proto__,如果不支持,Vue直接把arrayMethods身上的改写方法设置到被侦测的数组上

  1. 还是需要考虑一个问题,数组改变了去通知谁?如何收集依赖?在哪里触发依赖?
    需要注意的一个问题是data是一个对象,如果有数组也是存在对象中的,所以数组是在getter中收集依赖,在拦截器中触发依赖
    数组中元素的修改时通过拦截器重写的方法,那么如果触发了重写方法说明数据改变了,此时我们就需要通知依赖,所以是在拦截器中触发依赖。

  2. 拦截器怎么能看见依赖?我们要把依赖放在哪里?
    object是一个属性对应一个Dep数组,所以写在了defineReactive函数中,因为需要在getter中收集依赖,在setter中触发依赖,在getter、setter的时候需要看得见依赖。
    同理,我们需要把依赖保存在getter和拦截器都能看见依赖的地方,也就是Observer实例中。
    因为在getter中可以访问到Observer实例,在拦截器中也可以访问到Observer实例(这个地方在后面讲,先认定这个结论)

//引入arrayMethods
class Observer{
	constructor(data){
		//....
		this.dep = new Dep();//在Observer实例上新增dep
		if(Array.isArray(data)){//为数组增加响应式
			data.__proto__ = arrayMethods;
		}
		else{ //为对象增加响应式
			//....
		}	
	}
}

怎么在拦截器中访问到Oberver实例?给数组增加一个属性__ob__,这个__ob__指向Oberver实例

//工具函数给obj身上的key添加val
function def(obj,key,val,enumerable){
	Object.defineProperty(obj,key,{
		value:val,
		enumerable:!!enumerable,
		writeable:true,
		configurable:true
	})
}
class Observer{
	constructor(data){
		//....
		this.dep = new Dep();//在Observer实例上新增dep
		def(data,'__ob__',this); //拦截器可以通过数组身上的__ob__属性访问到Observer实例
		if(Array.isArray(data)){//为数组增加响应式
			data.__proto__ = arrayMethods;
		}
		else{ //为对象增加响应式
			//....
		}	
	}
}

现在我们在可以在拦截器里看见依赖了,也就是通过数组身上的__ob__属性,那么我们就可以在拦截器中通知依赖,告诉Watcher数据变啦。

[...].forEach(function(method){
	const original = arrayProto[method];//缓存Array原型上的方法
	def(arrayMethods,method,function mutator(...args){
		const result = original.apply(this,args);//this指向拦截器
		this.__ob__.dep.notify(); //向依赖发送数据!!!
		return result;
	})
})
  1. 在getter收集依赖,添加到observer实例的dep实例中
function defineReactive(data,key,val){
	//if(typeof val === 'object')new Observer(val);
	let childOb = observe(val); //上面的判断也会在这个函数中判断
	let dep = new Dep(); //绑定dep对象
	Objcet.defineProperty(data,key,{
		enumerable:true,
		configurable:true,
		get:function(){
			dep.depend();//对象的依赖收集
			if(childOb){
				childOb.dep.depend(); //数组的依赖收集,收集在observer实例的Dep上
			}
			return val;
		},
		set:function(){
			if(val===newVal)return;
			val = newVal;
			dep.notify();//通知依赖,数据发生了修改
		}
	})
}
export function observe (value){ //observe函数:为数组和对象返回observe实例
	if(!isObject(value))return;
	let ob
	if(hasOwn(value,'__ob__') && value.__ob___ instanceof Observer){//如果该数组已经有了__ob__,已经创建了observer实例,已经是响应式的了
		ob = value.__ob__;
	}else{ //数组没有observer实例 或者是对象则会创建observer实例
		ob = new Observer(value);
	}
	return ob;
}

__ob__的作用
1.让拦截器可以访问到observer实例:通过将observer实例绑定在数组的__ob__属性上
2.用来标识当前value是否已经被Observer类转化为响应式数据

  1. 如果数组里面套对象怎么办?侦察Array中的每一项
class Observer{
	constructor(data){
		//...
		if(Array.isArray(value)){
			this.observeArray(value); //侦察Array中的每一项
			//...
		}
		}
		//....
		observeArray(items){//循环侦察Array中的每一项
			for(let i=0,l=items.length;i<1;i++){
				observe(items[i]);//observe函数:为数组和对象返回observe实例
			}
		
	}
  1. 还有一点是如果push、unshift、splice新增数组元素进来,那么对于新增的元素我们也需要用Observe来侦测,让其变为响应式的。具体实现是把新增元素取出来,调用数组身上的observer实例的observeArray方法
对象和数组响应式原理的对比与整理

在哪里收集依赖在哪里通知依赖?

  • 对象:在getter中收集依赖,在setter中触发依赖
  • 数组:在getter中收集依赖,在拦截器对象中触发依赖

哪里创建Dep实例
在收集依赖的地方和触发依赖的地方都能看见

  • 对象:在defineReactive函数里创建Dep实例,每一个属性对应一个Dep实例
  • 数组:在Observer类里给Observer实例绑定Dep实例,每一个observer实例对应一个Dep实例

Observer类:侦察对象和数组的变化
主要目的:为数组和对象增加响应式
思路
1.为数组准备好Dep实例和为数组添加__ob__属性,该属性指向Observer实例,每一个数组都会绑定一个observer实例

__ob__的作用
1.让拦截器可以访问到observer实例:通过将observer实例绑定在数组的__ob__属性上
2.用来标识当前value是否已经被Observer类转化为响应式数据

2.循环侦察Array中的每一项,让数组的隐式原型指向拦截器对象

observeArray方法:循环侦察Array中的每一项,数组中的每一项调用observe函数
observe函数:为数组和对象返回observe实例

3.循环将对象中的每一个属性都转化为getter/setter模式

defineReactive:将对象的每一个属性转化为getter/setter,为对象的 对象的每一个属性对应一个Dep实例,对象的Dep实例在这个函数中创建。在getter收集依赖,在setter中触发依赖通知Dep
数组在getter中通过observer实例的Dep实例收集依赖

//工具函数给obj身上的key添加val
function def(obj,key,val,enumerable){
	Object.defineProperty(obj,key,{
		value:val,
		enumerable:!!enumerable,
		writeable:true,
		configurable:true
	})
}
class Observer{
	constructor(data){
		this.dep = new Dep();//在Observer实例上新增dep
		def(data,'__ob__',this); //为数组绑定observer实例
		if(Array.isArray(value)){//处理数组
			this.observeArray(value); //侦察Array中的每一项
			data.__proto__ = arrayMethods; //让数组的隐式原型指向拦截器对象
		}
		else{ //处理对象
			Object.keys(data).forEach(key=>{//循环将每一个属性转化为getter/setter
				defineReactive(this.data,key,data[key]);
			})
		}
    }
    observeArray(items){//循环侦察Array中的每一项
		for(let i=0,l=items.length;i<1;i++){
			observe(items[i]);//observe函数:为数组和对象返回observe实例
		}
}
function defineReactive(data,key,val){
		let childOb = observe(val); //返回observe实例
	    let dep = new Dep(); //绑定dep对象
		Objcet.defineProperty(data,key,{
			enumerable:true,
			configurable:true,
			get:function(){
				dep.depend();//对象的依赖收集
			if(childOb){
				childOb.dep.depend(); //数组的依赖收集,收集在observer实例的Dep上
			}
			return val;
		},
		set:function(){
			if(val===newVal)return;
			val = newVal;
			dep.notify();//通知依赖,数据发生了修改
		}
	})
}
}
export function observe (value){ //observe函数:为数组和对象返回observe实例
	if(!isObject(value))return;
	let ob
	if(hasOwn(value,'__ob__') && value.__ob___ instanceof Observer){//如果该数组已经有了__ob__,已经创建了observer实例,已经是响应式的了
		ob = value.__ob__;
	}else{ //数组没有observer实例 或者是对象则会创建observer实例
		ob = new Observer(value);
	}
	return ob;
}

数组专有:拦截器:重写七个方法,使其加入响应式

拦截器的作用
1.拦截数组的七个方法,将拦截器的__proto__隐式原型指向Array.prototype显式原型
2.通知依赖数据改变了,通过调用数组绑定的observer对象找到observer对象绑定的dep实例通知依赖

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto); //arrayMethods.__proto__ = Array.prototype

const methodsNeedChange = ['push','pop','shift','unshift','splice','sort','reverse']; //需要被改写的七个方法
methodsNeedChange.forEach(function(method){
	const original= arrayProto[method]; //缓存原来的方法,最终还是会被调用
	def(arrayMethods,method,function mutator(...args){
		const result = original.apply(this,args);//this指向拦截器,调用原型的方法
		this.__ob__.dep.notify(); //向依赖发送数据!!!
		return result;
	})
}

共有 Dep类:对依赖进行管理 没有对数组和对象分别处理

1.使用数据的时候收集依赖
2.通知依赖数据发生变化
3.对依赖进行管理,比如增加依赖、删除依赖、通知依赖等
Dep实例和数据的关系是一一对应的

class Dep {//Dep类存储依赖,添加依赖, 删除依赖,通知依赖等
	constructor(){
		this.subs = [];//subs存储依赖
	}
	addSub(sub){
		this.subs.push(sub);
	}
	depend(){
		if(window.target){
			this.addSub(window.target);
		}
	}
	notify(){
		this.subs.forEach(sub =>{
			sub.update(); //通知这个属性的所有依赖数据更新
		})
	}
}

共有 Watcher类:依赖 没有对数组和对象分别处理

中介,数据发生改变通知外界。外界通过Watcher类读取数据
当模板解析时会创建Watcher实例,此时调用update方法,从vm中取某个属性,相当于调用该属性的getter方法,此时window.target是有值的,值为watcher对象,所以这个watcher对象会被存储进dep.sub数组里面
Dep类通知依赖时也会调用update方法

export class Watcher{
	constructor(vm,name,node){
		this.node = node;
		this.vm = vm;
		this.name = name;
		window.target = this, //我们给依赖命名为window.target, = watcher对象,所以依赖就是watcher对象
		this.update();
		window.target = null; //并没有绑定在实例上,全局仅有一个,数据更新后会重新调用get,防止一个watcher对象被多次加入dep.sub数组中
	}
	update(){//将{{name}}的name更新为vm里面的值,这里是在vm上代理了_data的值
		this.node.nodeValue = this.vm[this.name]; 
		//从vm中取某个属性,相当于调用该属性的getter方法,此时window.target是有值的,值为watcher对象,所以这个watcher对象会被存储进dep.sub数组里面
	}
}
//watcher对象在解析模板中的指令的时候会被创建new Watcher

主要的实现方式描述/总结

  • 对象
    new Vue()首先执行初始化,数据传给Observer类,该类的作用是把一个object中的所有属性(包括子属性)都转换成响应式的,它利用defineProperty方法,为对象中的每个属性绑定getter和setter方法以及dep实例,dep实例和属性一一对应,它的作用是对该属性的依赖进行管理,比如·在getter方法里收集依赖,在setter方法里触发依赖,利用dep通知watcher数据发生改变,watcher再通知界面数据发生改变(数据变化之后,通过patch方法来进行视图渲染)
    在这里插入图片描述
  • 数组:加入拦截器
    修改该数组的隐式原型__proto__指向拦截器的显式原型,拦截器对象的隐式原型__proto__指向Array.prototype,在拦截器中拦截数组中的七个方法,进行一些处理后,再调用Array.prototype上的对应方法。这样调用数组中的七个方法会先调用拦截器中重写的方法,调用重写方法时说明数组发生了变化,在重写方法中会通知该数组oberver实例上dep数据修改了,dep通知依赖(watcher)数据修改了,最后watcher通知界面数据修改了。依赖的收集是在getter中进行的,依赖的触发在拦截器中进行的。
    在这里插入图片描述

vue3响应式原理

质上是通过 Proxy 劫持了数据对象的读写,当我们访问数据时,会触发 getter 执行依赖收集;修改数据时,会触发 setter 派发通知。

  1. 副作用函数effect() ,effect的执行会直接或间接影响其他函数的执行。
//当obj发生变化时,effect函数自动重新执行,我们就称obj是响应式的
const obj = {text:'xxx'}
function effect(){
	console.log(obj.text);
}
//手动执行
obj.text = 'yyy'
effect()
  1. 通过 track函数 + trigger函数实现依赖的收集和触发,实现当数据更新时,他的依赖变量也跟着改变
    track函数:track函数收集依赖,对象的每个属性都有自己的dep,将所有依赖于该属性的**effect函数**都收集起来,放在dep里。
    trigger函数: trigger函数通知依赖。将依赖搜集起来之后,只要变量一改变,就执行trigger函数通知dep里所有依赖该变量的effect函数执行,实现依赖变量的更新。

1.对象的每个属性都有自己的dep,将所有依赖于该属性的**effect函数**都收集起来,放在dep里。
2.每个对象会建立一个Map来存储各个属性和dep的对应关系,key为属性名,value为该属性的dep(使用Set来存储)。
3.使用WeakMap来存储多个对象的Map

在这里插入图片描述

  1. Proxy-Reflect实现自动收集和触发依赖track-trigger函数
//reactive 将对象变成响应式的
function reactive(target) {
    const handler = {
        get(target, key, receiver) {
            track(receiver, key) // 访问时收集依赖
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            Reflect.set(target, key, value, receiver)
            trigger(receiver, key) // 设值时自动通知更新
        }
    }

    return new Proxy(target, handler)
}

响应式代码

function reactive(target = {}) {
  if (typeof target !== "object" || target == null) {
    return target
  }

  // 代理配置
  const proxyConf = {
    get(target, key, receiver) {
      //只监听对象本身(非原型)属性
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        //如果是本身的属性就监听,如果是对象原型的属性就不监听
        console.log("get", key)
      }
        
      const result = Reflect.get(target, key, receiver)
      //(惰性)深度监听-->提升性能
      return reactive(result)
    },
    set(target, key, val, receiver) {
      // 重复的数据不处理
      if (val === target[key]) {
        return true
      }

      // 监听是否是新增的key
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        console.log("已有的key", key)
      } else {
        console.log("新增的key", key)
      }

      const result = Reflect.set(target, key, val, receiver)
      return result //通过return的值可以看出是否设置成功
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key)
      console.log("delete property", key)
      return result //是否删除成功
    },
  }

  // 生成代理对象
  const observed = new Proxy(target, proxyConf)
  return observed
}

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值