歡迎關注個人博客:https://github.com/wangweianger/myblog前端
Vue內部實現了一組觀察數組的變異方法,例如:push(),pop(),shift()等。
react
Object.definePropert只能把對象屬性改成getter/setter,而對於數組的方法就無能爲力了,其內部巧妙的使用了數組的屬性來實現了數據的雙向綁定,下面咱們來一步一步的實現一個簡單版。git
下文大部分代碼摘自Vue源碼github
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 = [] 綁定起來呢,來看看下面的實現?數組
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 走了。bash
obarr.push(0)
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賦值代碼以下:app
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'})