VUE的一个特点就是数据响应式;当我们改变一个数据时,页面中所有用到该数据的地方都会发生改变;那么在这个过程中VUE都做了什么工作呢
我们可以简单模拟一下vue的响应式的核心代码
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | //VUE初始化阶段initData()函数会将data数据代理到vm._data中 let vm_data = { 	name:'lele', 	age:18 }; //二.通过订阅者发布者模式,通知需要改变数据的节点数据 //发布者 (依赖搜集器) class Dep{ 	constructor(){ 		this.subs = []//订阅者列表 	}; 	addSub(watcher){ 		this.subs.push(watcher) 	}; 	notify(){ 		this.subs.forEach(item=>{ 			item.update() 		}) 	} } //订阅者 class watcher{ 	constructor(name){ 		this.name = name; 	} 	update(){//在数据变化的时候会 		console.log(this.name + '发生了update') 	} } const dep = new Dep();//在数据初始化的时候每一个data属性都是一个依赖收集器 //一.通过Object.defineProperty()里的set和get函数,知道哪些数据发生了改变 Object.keys(vm_data).forEach(key => {//劫持vm._data数据 	let value = vm_data[key]; 	Object.defineProperty(vm_data,key,{ 		set(newvalue){//改变属性的时候执行  进行发布更新 			console.log(`我设置了${key}属性`); 			value = newvalue; 			dep.notify();//改变属性的时候就通知所有相关依赖发生变化 		}, 		get(){//获取属性的时候执行  进行依赖搜集 			//在complime模板编译的时候会为每一条数据添加一个watcher对象 			let w1 = new watcher('message1');//模拟编译的时候添加的watcher 			let w2 = new watcher('message2'); 			dep.addSub(w1);//模拟将每一个wahcher搜集到对应的依赖里 			dep.addSub(w2); 			console.log(`我获取了${key}属性`) 			return value 		} 	}); }) console.log(vm_data.name);//我获取了age属性  lele console.log(vm_data.age = 20);//我设置了age属性  20 | 

vue2响应原理的缺点
vue3的响应原理与vue2大致是一样的都是类似于发布者订阅者模式;vue3改用了proxy代理方式实现,然后通过proxy的get方法里的track函数进行依赖搜集,通过set方法里的trigger进行触发依赖通知。
我们接下来通过简单实现vue3的reactive来了解一下vue3是如何进行响应式的
proxy的基本使用:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | const obj= { a: 0 }; const objProxy = new Proxy(obj, {   get(target, key) {   console.log('这里触发get,触发依赖搜集')   return Reflect.get(target, key)   },   set(target, key, value) {   console.log('这里触发set,触发依赖通知')   return Reflect.set(target, key, value)   }, }); console.log(objProxy.a) // 触发get objProxy.a= 666 // 触发set | 
这里的Reflect是es6新增的一个对Object映射的一个内置对象。Reflect定义了Object上的十几种个静态拦截方法(get,set,construct,apply,has,ownKeys,defineProperty,deleteProperty,getOwnPropertyDescriptor,getPrototypeOf,setPrototypeOf,isExtensible,preventExtensions,setPrototypeOf)。使其有更好的规范性、易读性和扩展性。在es6中Reflect可以和proxy完美搭配使用。
reactive的简单实现:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function reactive(obj) {     //判斷是否為對象     if (typeof obj !== 'object' && typeof obj !== 'function')  return console.warn(`${obj} not object`)     return new Proxy(obj, {       get(target, key) {         const res = Reflect.get(target, key)         track(target, key)//搜集依赖         return res       },       set(target, key, value) {         const res = Reflect.set(target, key, value)         trigger(target, key, value)//通知依赖         return res //这是个布尔值       }     })   } | 
reactive的简单完整实现 :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | let activeEffect; //reactive函数 function reactive(obj) {   //判斷是否為對象   if (typeof obj !== 'object' && typeof obj !== 'function')  return console.warn(`${obj} not object`)   return new Proxy(obj, {     get(target, key) {       const res = Reflect.get(target, key)       track(target, key)//搜集依赖       return res     },     set(target, key, value) {       const res = Reflect.set(target, key, value)       trigger(target, key, value)//通知依赖       return res //这是个布尔值     }   }) } //实现依赖类和effect函数 class ReactiveEffect {   _fn_;   constructor(fn) {     this._fn_ = fn   }   run() {     activeEffect = this;     this._fn_();   } } //effect副作用函数,这个函数在响应式数据发生变化时会被自动触发,从而执行相关的操作 function effect(fn) {   const _effect = new ReactiveEffect(fn)   _effect.run() } const targetMap = new WeakMap() //收集依赖 function track(target, key) {   let depsMap = targetMap.get(target)   if (!depsMap) {     depsMap = new Map()     targetMap.set(target, depsMap)   }   let dep = depsMap.get(key);   if (!dep) {     dep = new Set()     depsMap.set(key, dep)   }   dep.add(activeEffect) } //触发依赖 function trigger(target, key) {   const depsMap = targetMap.get(target)   const deps = depsMap.get(key)   for (const effect of deps) {     effect.run()   } } |