欢迎关注个人博客:https://github.com/wangweianger/myblogjavascript
Vue内部实现了一组观察数组的变异方法,例如:push(),pop(),shift()等。java
Object.definePropert只能把对象属性改成getter/setter,而对于数组的方法就无能为力了,其内部巧妙的使用了数组的属性来实现了数据的双向绑定,下面咱们来一步一步的实现一个简单版。react
下文大部分代码摘自Vue源码git
let obarr = []
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto)
Object.defineProperty(arrayMethods,'push',{ value:function mutator(){ console.log('obarr.push会走这里') } })
此时arrayMethods定义了一个push的新属性,那么咱们如何把它和 let obarr = [] 绑定起来呢,来看看下面的实现?github
obarr.__proto__ = arrayMethods
使用arrayMethods覆盖obarr的全部方法数组
到此如今完整代码以下:浏览器
let obarr = [] const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) Object.defineProperty(arrayMethods,'push',{ value:function mutator(){ console.log('obarr.push会走这里') } }) obarr.__proto__ = arrayMethods;
向obarr中push一个值看看,是否是走了console呢,确定的答复你:yes 走了。缓存
obarr.push(0)
针对于不支持__proto__的浏览器实现以下:app
let obarr = [] const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) Object.defineProperty(arrayMethods,'push',{ value:function mutator(){ console.log('obarr.push会走这里') } }) Object.defineProperty(obarr,'push',{ value:arrayMethods.push })
来真正的为arr赋值代码以下:函数
let obarr = [] const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) Object.defineProperty(arrayMethods,'push',{ value:function mutator(){ //缓存原生方法,以后调用 const original = arrayProto['push'] let args = Array.from(arguments) original.apply(this,args) console.log(obarr) } }) obarr.__proto__ = arrayMethods;
如今每次执行obarr.push(0)时,obarr都会新增一项。
上面实现了push方法,其余的方法同理,咱们只须要把全部须要实现的方法循环遍历执行便可,升级后代码以下:
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(item=>{ Object.defineProperty(arrayMethods,item,{ value:function mutator(){ //缓存原生方法,以后调用 const original = arrayProto[item] let args = Array.from(arguments) original.apply(this,args) }, }) }) function protoAugment (target,src) { target.__proto__ = src } // 调用 let obarr = [] protoAugment(obarr, arrayMethods)
来多试几回吧:
obarr.push(1) obarr.push(2) obarr.push(3) obarr.push(4)
分析:
一、通过以上的代码能够看出,只会更改咱们给定数组(obarr)的相关方法,而不会污染Array的原生方法,所以其余普通数组不受影响。
二、重新赋值数组的__proto__属性为arrayMethods,而arrayMethods咱们重新定义了push,pop等相关属性方法,所以当咱们使用数组的push,pop等方法时会调用arrayMethods的相关属性方法,达到监听数组变化的能力。
三、对于不支持__proto__属性的浏览器,直接使用Object.defineProperty重新定义相关属性。
四、而Vue的实现方法正如上,更改咱们须要监听的Array数组属性值(属性值为函数),在监听函数里执行数组的原生方法,并通知全部注册的观察者进行响应式处理。
实现Vue的数据双向绑定有3大核心:Observer,Dep,Watcher,来个简单实现
首先来实现dep,dep主要负责依赖的收集,get时触发收集,set时通知watcher通讯:
class Dep{ constructor () { // 存放全部的监听watcher this.subs = [] } //添加一个观察者对象 addSub (Watcher) { this.subs.push(Watcher) } //依赖收集 depend () { //Dep.target 做用只有须要的才会收集依赖 if (Dep.target) { Dep.target.addDep(this) } } // 调用依赖收集的Watcher更新 notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // 为Dep.target 赋值 function pushTarget (Watcher) { Dep.target = Watcher }
再来简单的实现Watcher,Watcher负责数据变动以后调用Vue的diff进行视图的更新:
class Watcher{ constructor(vm,expOrFn,cb,options){ //传进来的对象 例如Vue this.vm = vm //在Vue中cb是更新视图的核心,调用diff并更新视图的过程 this.cb = cb //收集Deps,用于移除监听 this.newDeps = [] this.getter = expOrFn //设置Dep.target的值,依赖收集时的watcher对象 this.value =this.get() } get(){ //设置Dep.target值,用以依赖收集 pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) return value } //添加依赖 addDep (dep) { // 这里简单处理,在Vue中作了重复筛选,即依赖只收集一次,不重复收集依赖 this.newDeps.push(dep) dep.addSub(this) } //更新 update () { this.run() } //更新视图 run(){ //这里只作简单的console.log 处理,在Vue中会调用diff过程从而更新视图 console.log(`这里会去执行Vue的diff相关方法,进而更新数据`) } }
简单实现Observer,Observer负责数据的双向绑定,并把对象属性改成getter/setter
//得到arrayMethods对象上全部属性的数组 const arrayKeys = Object.getOwnPropertyNames(arrayMethods) class Observer{ constructor (value) { this.value = value // 增长dep属性(处理数组时能够直接调用) this.dep = new Dep() //将Observer实例绑定到data的__ob__属性上面去,后期若是oberve时直接使用,不须要重新Observer, //处理数组是也可直接获取Observer对象 def(value, '__ob__', this) if (Array.isArray(value)) { //处理数组 const augment = value.__proto__ ? protoAugment : copyAugment //此处的 arrayMethods 就是上面使用Object.defineProperty处理过 augment(value, arrayMethods, arrayKeys) // 循环遍历数组children进行oberve this.observeArray(value) } else { //处理对象 this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { //此处我作了拦截处理,防止死循环,Vue中在oberve函数中进行的处理 if(keys[i]=='__ob__') return; defineReactive(obj, keys[i], obj[keys[i]]) } } observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } //数据重复Observer function observe(value){ if(typeof(value) != 'object' ) return; let ob = new Observer(value) return ob; } // 把对象属性改成getter/setter,并收集依赖 function defineReactive (obj,key,val) { const dep = new Dep() //处理children let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { console.log(`调用get获取值,值为${val}`) const value = val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } //此处是对Array数据类型的依赖收集 if (Array.isArray(value)) { dependArray(value) } } return value }, set: function reactiveSetter (newVal) { console.log(`调用了set,值为${newVal}`) const value = val val = newVal //对新值进行observe childOb = observe(newVal) //通知dep调用,循环调用手机的Watcher依赖,进行视图的更新 dep.notify() } }) } //辅助方法 function def (obj, key, val) { Object.defineProperty(obj, key, { value: val, enumerable: true, writable: true, configurable: true }) } //从新赋值Array的__proto__属性 function protoAugment (target,src) { target.__proto__ = src } //不支持__proto__的直接修改相关属性方法 function copyAugment (target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } } //收集数组的依赖 function dependArray (value) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { //循环遍历chindren进行依赖收集 dependArray(e) } } }
Observer中写了一些相关须要的方法。
让咱们来修改下处理数组的相关方法,当使用Array.push相关方法时能够调用Watcher更新视图
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(item=>{ Object.defineProperty(arrayMethods,item,{ value:function mutator(){ //缓存原生方法,以后调用 const original = arrayProto[item] let args = Array.from(arguments) original.apply(this,args) const ob = this.__ob__ ob.dep.notify() }, }) })
/*----------------------------------------处理数组------------------------------------*/ const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(item=>{ Object.defineProperty(arrayMethods,item,{ value:function mutator(){ //缓存原生方法,以后调用 const original = arrayProto[item] let args = Array.from(arguments) original.apply(this,args) const ob = this.__ob__ ob.dep.notify() }, }) }) /*----------------------------------------Dep---------------------------------------*/ class Dep{ constructor () { // 存放全部的监听watcher this.subs = [] } //添加一个观察者对象 addSub (Watcher) { this.subs.push(Watcher) } //依赖收集 depend () { //Dep.target 做用只有须要的才会收集依赖 if (Dep.target) { Dep.target.addDep(this) } } // 调用依赖收集的Watcher更新 notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // 为Dep.target 赋值 function pushTarget (Watcher) { Dep.target = Watcher } /*----------------------------------------Watcher------------------------------------*/ class Watcher{ constructor(vm,expOrFn,cb,options){ //传进来的对象 例如Vue this.vm = vm //在Vue中cb是更新视图的核心,调用diff并更新视图的过程 this.cb = cb //收集Deps,用于移除监听 this.newDeps = [] this.getter = expOrFn //设置Dep.target的值,依赖收集时的watcher对象 this.value =this.get() } get(){ //设置Dep.target值,用以依赖收集 pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) return value } //添加依赖 addDep (dep) { // 这里简单处理,在Vue中作了重复筛选,即依赖只收集一次,不重复收集依赖 this.newDeps.push(dep) dep.addSub(this) } //更新 update () { this.run() } //更新视图 run(){ //这里只作简单的console.log 处理,在Vue中会调用diff过程从而更新视图 console.log(`这里会去执行Vue的diff相关方法,进而更新数据`) } } /*----------------------------------------Observer------------------------------------*/ //得到arrayMethods对象上全部属性的数组 const arrayKeys = Object.getOwnPropertyNames(arrayMethods) class Observer{ constructor (value) { this.value = value // 增长dep属性(处理数组时能够直接调用) this.dep = new Dep() //将Observer实例绑定到data的__ob__属性上面去,后期若是oberve时直接使用,不须要重新Observer, //处理数组是也可直接获取Observer对象 def(value, '__ob__', this) if (Array.isArray(value)) { //处理数组 const augment = value.__proto__ ? protoAugment : copyAugment //此处的 arrayMethods 就是上面使用Object.defineProperty处理过 augment(value, arrayMethods, arrayKeys) // 循环遍历数组children进行oberve this.observeArray(value) } else { //处理对象 this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { //此处我作了拦截处理,防止死循环,Vue中在oberve函数中进行的处理 if(keys[i]=='__ob__') return; defineReactive(obj, keys[i], obj[keys[i]]) } } observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } //数据重复Observer function observe(value){ if(typeof(value) != 'object' ) return; let ob = new Observer(value) return ob; } // 把对象属性改成getter/setter,并收集依赖 function defineReactive (obj,key,val) { const dep = new Dep() //处理children let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { console.log(`调用get获取值,值为${val}`) const value = val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } //此处是对Array数据类型的依赖收集 if (Array.isArray(value)) { dependArray(value) } } return value }, set: function reactiveSetter (newVal) { console.log(`调用了set,值为${newVal}`) const value = val val = newVal //对新值进行observe childOb = observe(newVal) //通知dep调用,循环调用手机的Watcher依赖,进行视图的更新 dep.notify() } }) } //辅助方法 function def (obj, key, val) { Object.defineProperty(obj, key, { value: val, enumerable: true, writable: true, configurable: true }) } //从新赋值Array的__proto__属性 function protoAugment (target,src) { target.__proto__ = src } //不支持__proto__的直接修改相关属性方法 function copyAugment (target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } } //收集数组的依赖 function dependArray (value) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { //循环遍历chindren进行依赖收集 dependArray(e) } } }
定义一个data对象:
let data={ name:'zane', blog:'https://blog.seosiwei.com/', hobby:['basketball','football'], list:[ {name:'zhangsan'}, {name:'lishi'} ] }
调用watcher,并进行数据监听
let getUpdates = (vm)=>{ console.log('默认调用一次,进行依赖收集') } new Watcher(this,getUpdates) observe(data)
调用get收集依赖
//收集name依赖 data.name //收集hobby依赖 data.hobby
测试数据监听
//都会打印这里会去执行Vue的diff相关方法,进而更新数据 data.name = 'zhangshan' data.hobby.push('volleyball')
是不时出现可可爱的 这里会去执行Vue的diff相关方法,进而更新数据 日志呢。
没进行依赖收集的属性会打印日志吗,来尝试一下吧
//不会打印更新 data.blog = 'http://www.seosiwei.com/' //不会调用每个children的打印更新 data.list.push({name:'xiaowang'})