原理(进阶——vue2)
一.defineReactive函数——响应式操作
1.Object.defineProperty()函数
要进行响应式操作就需要使用Object对象上的一个方法defineProperty()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Object .defineProperty (obj,key,{ configurable :true , enumerable :true , writable :true , get ( ){ return ; }, set (val ){ console .log (val); } })
而我们的defineReactive函数则是对defineProperty进行了简单的封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function defineReactive (obj,key,value ) { if (arguments .length == 2 ){ value = obj[key] } Object .defineProperty (obj,key,{ configurable :true , enumerable :true , get ( ){ console .log ('访问' +key+'属性' ); return value; }, set (val ){ console .log ('修改' +key+'属性' ); value = val } }) }
二.Observer类——观察者
1.observe函数
我们需要一个函数来判断一个数据是否需要为其进行响应式操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function observe (value ) { if (typeof value !== 'object' ){ return ; }else { let ob if (value.__ob__ ){ ob = value.__ob__ }else { ob = new Observer (value) } } }
我们需要在defineReactive函数内部去调用它 1.给子对象添加观察 2.给新的值添加观察
1 2 3 4 5 6 7 8 9 10 11 12 function defineReactive (obj,key,value ) { let childOb = observe (value) Object .defineProperty (obj,key,{ get ( ){ }, set (val ){ childOb = observe (val) } }) }
2.def函数
我们需要在Oberver中对传入的对象/数组添加一个属性,属性中存放一个Oberver实例用于观察这个对象的数据变化
1 2 3 4 5 function def (obj,key,value ) { Object .defineProperty (obj,key,{ value }) }
3.Observer类
在Observer类中我们需要对属性进行观察 并且我们需要在observe中实例化他
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Observer { constructor (value ){ def (value,'__ob__' ,this ) if (Array .isArray (value)){ this .arrWalk (value) Object .setPrototypeOf (value,arrMethods); }else { this .walk (value) } } walk (obj ){ for (const key in obj) { defineReactive (obj,key) } } arrWalk (arr ){ } }
4.数组的观察——重写方法
由于数组与对象不同 defineReactive无法监测他的增删添改 因此我们需要对数组原型上的七种方法进行重写,方便我们监听它
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 const methodsNeedChange = [ 'push' , 'pop' , 'shift' , 'unshift' , 'splice' , 'sort' , 'reverse' ]let arrMethods = Object .create (Array .prototype ) methodsNeedChange.forEach ( methods => { let oldMethods = Array .prototype [methods] def (arrMethods,methods,function ( ) { let result = oldMethods.apply (this ,arguments ) let newArr = [] let ob = this .__ob__ let arr = [...arguments ] switch (methods) { case 'push' : case 'unshift' : newArr = arguments break ; case 'slice' : newArr = arr.slice (2 ) break ; } if (newArr){ console .log ('使用了' + oldMethods.name +'方法' ); ob.arrWalk (newArr) } return result }) } )
而此时arrMethods上就存放了可以被监听的七种方法 我们在需要这七种方法时,也就是observe时将传数组的原型改成arrMethods,这样我们的数组调用数组的方法也就能够被监听到 如下:
1 2 3 4 5 6 arrWalk (arr ){ for (let i = 0 ,l = arr.length ;i<l;i++){ observe (arr[i]); } }
三.Dep类——依赖收集
在vue中,可能我们在vue实例内部定义的数据不一定会同时使用数据,我们没必要给未使用的数据进行响应式处理.因此我们需要判断那些数据是正在被使用的,然后打上标记,这个标记要求是在全局唯一的位置进行存放.一般来说我们选择Dep类,标记为Dep.target当然你也可以选择window对象来存放.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Dep { constructor ( ){ this .subs = [] } notify ( ){ let sub = this .subs .slice () for (let i = 0 ; i < sub.length ; i++){ sub[i].update () } } depend ( ){ if (Dep .target ){ this .addSub (Dep .target ) } } addSub (sub ){ this .subs .push (sub) } }
我们需要在defineReactive函数,Observer类中实例化Dep类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function defineReactive (obj,key,val ) { let dep = new Dep () Object .defineProperty (obj,key,{ get ( ){ if (Dep .target ){ dep.depend () if (childOb){ childOb.dep .depend () } } return val }, set (newValue ){ dep.notify () } }) }class Observer { constructor (value ){ this .dep = new Dep () } }
四.Watcher类——订阅者
Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Dep的subs中,数据变动的时候Dep会通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。
1.Watcher类
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 class Watcher { constructor (target,expression,callback ){ this .target = target this .getter = parsePath (expression) this .callback = callback this .value = this .get () } get ( ){ Dep .target = this let obj = this .target var value try { value = this .getter (obj) } catch (error) { throw new Error ('报错了~~~' ) }finally { Dep .target = null } return value } run ( ){ this .getAndInvoke (this .callback ) } update ( ){ this .run () } getAndInvoke (cb ){ let value = this .get () if (value !== this .value || typeof value == 'object' ){ let oldVlaue = this .value cb.call (this .target ,value,oldVlaue) } } }
2.parsePath函数——根据提供的路径寻找对象身上的属性
1 2 3 4 5 6 7 8 9 function parsePath (str ) { let arr = str.split ('.' ) return obj => { for (let i = 1 ; i<arr.length ; i++){ obj = obj[arr[i]] } return obj } }