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() } } |