最近在看「深刻淺出vuejs」,第一篇變化偵測,想把本身的理解總結一下。vue
Observer
類來實現this
當前的上下文;因此當數據變化時,咱們能夠通知他,觸發update
,從而觸發渲染Dep
類來實現class Observer { constructor(value) { this.value = value if(!Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for(let i = 0; i < keys.length; i++) { definedReactive(obj, keys[i], obj[keys[i]]) } } } function definedReactive(data, key, value) { if(typeof val === 'object') { new Observer(value) } let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() return value }, set: function (newVal) { if(value === newVal) { //這邊最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //這邊新的newVal若是是引用類型也應該進行進行new Observer() dep.notify() } }) }
data
對象進行遍歷設置其屬性描述對象get
的設置就是爲了在數據被訪問時,將依賴dep.depend()
進去,至於作了什麼看詳細看Dep類set
的設置則是爲了判斷新值和舊值是否同樣(注意NaN),若不同,則執行dep.notify()
,通知相應依賴進行更新變化class Dep { constructor () { this.subs = [] //存放依賴 } addSub () { this.subs.push(sub) }, remove () { remove(this.subs, sub) }, depend () { if(window.target) { this.addSub(window.target) //window.target 是this,watcher的上下文 } }, notify () { const subs = this.subs.slice() for(let i = 0, l = subs.length; i < l; i ++) { subs[i].update() //update這個方法來自watcher實例對象的方法 } } } function remove(arr, item) { if(arr.length) { const index = arr.indexOf(item) if(index > -1) { return arr.splice(index, 1) } } }
dep
實例對象的增刪改查的操做window.target
這個依賴怎麼來,就看watcher
實例對象了第一版:數組
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.getter = parsePath(expOrFn) this.cb = cb this.value = this.get() } get() { window.target = this let value = this.getter.call(this.vm, this.vm) window.target = undefined return value } update() { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
vm.$watch('data.a', function (newValue, oldValue) { //執行相關操做 })
parsePath(expOrFn)
作了什麼?從下面代碼中能夠看出做用就是返回一個函數,這個函數用來讀取value
值const bailRE = /[^\w.$]/ // function parsePath(path) { if(bailRE.test(path) { return //當path路徑中有一個字符不知足正則要求就直接return } return function () { const arr = path.split('.') let data = this for(let i = 0, l = arr.length; i < l; i ++) { let data = data.arr[i] } return data } }
new Watcher
時會執行this.value
,從而執行this.get()
,因此這時的window.target
是當前watcher
實例對象this
;接着執行this.getter.call(this.vm, this.vm)
,觸發屬性描述對象的get
方法,進行dep.depend()
,最後將其window.target = undefined
update
的方法是在數據改變後觸發,但這邊有個問題就是會重複添加依賴 key
進行監聽key-value
不會被監聽Observer
dep.notify()
,以及如何收集數組的依賴push, pop, shift, unshift, splice, sort, reverse
這幾個方法的封裝來觸發dep.notify()
_proto_
屬性的,直接改寫原型鏈的這些方法;第二種對於不支持的,直接在實例對象上添加改變後的7個方法const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) //新建對象,繼承Array的原型鏈 class Observer { constructor (value) { this.value = value this.dep = new Dep() //在Observer中添加dep屬性爲了記錄數組的依賴 def(value, "_ob_", this) //在當前value上新增`_ob_`屬性,其值爲this,當前observer實例對象 if(Array.isArray(value) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observerArray(value) //將數組內元素也進行Observer }else { this.walk(value) } } //新增 observerArray (items) { for(let i = 0, l = items.length; i < l; i ++) { observe(items[i]) } } } //做用就是爲obj,添加key值爲val function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) } function observe(value, asRootData) { if(!isObject(value)) { return } let ob //判斷value是否已是Observer實例對象,避免重複執行Observer if(hasOwn(value, "_ob_") && value._ob_ instanceof Observer) { ob = value._ob_ } else { ob = new Observer(value) } return ob } function definedReactive(data, key, value) { let childOb = observe(value) //修改 let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() if(childOb) { //新增 childOb.dep.depend() } return value }, set: function (newVal) { if(value === newVal) { //這邊最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //這邊新的newVal若是是引用類型也應該進行進行new Observer() dep.notify() } }) } //觸發數組攔截 ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator() { const result = original.apply(this, args) const ob = this._ob_ //this就是數據value let inserted //對於新增變化的元素頁進行observerArray() switch (method) { //由於這幾個是有參數的 case 'push': case 'unshift': //由於push和unshift都是同樣的取args,因此push不須要加break了 inserted = args break case 'splice': //新增變化元素是從索引2開始的 inserted = args.slice(2) break } ob.dep.notify() //通知依賴執行update return result }) }
data = { a: [1, 2, 3] }
爲例data
對象進行Observer
,將執行this.walk(data)
let childOb = observe(val)
,發現value
是一個數組對象,進行Observer
,主要進行是augment(value, arrayMethods, arrayKeys)
,將7個方法進行攔截,接着遍歷內部元素是否有引用數據類型,有繼續Observer
,最後返回Observer
實例對象ob
get
方法,當數據data被訪問時,首先執行dep.depend()
這裏將依賴添加到data
的dep
中;接着由於childOb
爲true
因此執行childOb.dep.depend()
,這裏是將依賴加入到observer
實例對象的dep
中,爲何,這個dep
是給數組發生變化時執行this._ob_.dep.notify()
,這個this就是value
對象,由於def(value, "_ob_", this)
,因此能夠執行dep.notify()
this.list.length = 0
進行清空時,不會觸發它的依賴更新,也就不會觸發視圖的渲染更新this.list[0] = 2
,這種經過索引來改變元素值時頁同樣不會觸發更新vm.$watch,vm.$set,vm.$delete
下篇中進行整理掘金地址app