理解Vue中Watch的實現原理和方式以前,你須要深刻的理解MVVM的實現原理,若是你還不是很理解,推薦你閱讀我以前的幾篇文章:vue
完全搞懂Vue針對數組和雙向綁定(MVVM)的處理方式
react
vue.js源碼解讀系列 - 雙向綁定具體如何初始化和工做
git
vue.js源碼解讀系列 - 剖析observer,dep,watch三者關係 如何具體的實現數據雙向綁定
github
也能夠關注個人博客查看關於Vue更多的源碼解析:github.com/wangweiange…數組
備註:bash
一、此文大部分代碼來自於Vue源碼ide
二、此文MVVM部分代碼來自於【完全搞懂Vue針對數組和雙向綁定(MVVM)的處理方式】,如有不懂之處,建議先看上文函數
三、部分代碼爲了兼容測試作了部分更改,但原理跟Vue一致測試
畫一張watch的簡單工做流程圖:ui
把上文的 Dep,Oberver,Wather拿過來並作部分更改(增長收集依賴去重處理):
//標識當前的Dep id
let uidep = 0
class Dep{
constructor () {
this.id = uidep++
// 存放全部的監聽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 = null
const targetStack = []
// 爲Dep.target 賦值
function pushTarget (Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = Watcher
}
function popTarget () {
Dep.target = targetStack.pop()
}複製代碼
//去重 防止重複收集
let uid = 0
class Watcher{
constructor(vm,expOrFn,cb,options){
//傳進來的對象 例如Vue
this.vm = vm
if (options) {
this.deep = !!options.deep
this.user = !!options.user
}else{
this.deep = this.user = false
}
//在Vue中cb是更新視圖的核心,調用diff並更新視圖的過程
this.cb = cb
this.id = ++uid
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
if (typeof expOrFn === 'function') {
//data依賴收集走此處
this.getter = expOrFn
} else {
//watch依賴走此處
this.getter = this.parsePath(expOrFn)
}
//設置Dep.target的值,依賴收集時的watcher對象
this.value =this.get()
}
get(){
//設置Dep.target值,用以依賴收集
pushTarget(this)
const vm = this.vm
//此處會進行依賴收集 會調用data數據的 get
let value = this.getter.call(vm, vm)
//深度監聽
if (this.deep) {
traverse(value)
}
popTarget()
return value
}
//添加依賴
addDep (dep) {
//去重
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
//收集watcher 每次data數據 set
//時會遍歷收集的watcher依賴進行相應視圖更新或執行watch監聽函數等操做
dep.addSub(this)
}
}
}
//更新
update () {
this.run()
}
//更新視圖
run(){
const value = this.get()
const oldValue = this.value
this.value = value
if (this.user) {
//watch 監聽走此處
this.cb.call(this.vm, value, oldValue)
}else{
//data 監聽走此處
//這裏只作簡單的console.log 處理,在Vue中會調用diff過程從而更新視圖
console.log(`這裏會去執行Vue的diff相關方法,進而更新數據`)
}
}
// 此方法得到每一個watch中key在data中對應的value值
//使用split('.')是爲了獲得 像'a.b.c' 這樣的監聽值
parsePath (path){
const bailRE = /[^w.$]/
if (bailRE.test(path)) return
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
//此處爲了兼容個人代碼作了一點修改
//此處使用新得到的值覆蓋傳入的值 所以可以處理 'a.b.c'這樣的監聽方式
if(i==0){
obj = obj.data[segments[i]]
}else{
obj = obj[segments[i]]
}
}
return obj
}
}
}
//深度監聽相關代碼 爲了兼容有一小點改動
const seenObjects = new Set()
function traverse (val) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse (val, seen) {
let i, keys
const isA = Array.isArray(val)
if (!isA && Object.prototype.toString.call(val)!= '[object Object]') return;
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--){
if(i == '__ob__') return;
_traverse(val[i], seen)
}
} else {
keys = Object.keys(val)
i = keys.length
while (i--){
if(keys[i] == '__ob__') return;
_traverse(val[keys[i]], seen)
}
}
}複製代碼
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)) {
//這裏只測試對象
} 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]])
}
}
}
//數據重複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()
}
}
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
})
}複製代碼
class stateWatch{
constructor (vm, watch) {
this.vm = vm
//初始化watch
this.initWatch(vm, watch)
}
initWatch (vm, watch) {
//遍歷watch對象
for (const key in watch) {
const handler = watch[key]
//數組則遍歷進行createWatcher
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
this.createWatcher(vm, key, handler[i])
}
} else {
this.createWatcher(vm, key, handler)
}
}
}
createWatcher (vm, key, handler) {
let options
if (Object.prototype.toString.call(handler) == '[object Object]' ) {
//處理對象
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
vm.$watch(key, handler, options)
}
}複製代碼
新建一個Vue構造函數:
function Vue(){
}複製代碼
爲Vue新增原型方法$watch代碼以下:
Vue.prototype.$watch=function(expOrFn,cb,options){
const vm = this
options = options || {}
//此參數用於給data重新賦值時走watch的監聽函數
options.user = true
//watch依賴收集的Watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
//immediate=true時 會調用一次 watcher.run 方法,所以會調用一次watch中相關key的函數
if (options.immediate) {
cb.call(vm, watcher.value)
}
//返回一個取消監聽的函數
return function unwatchFn () {
watcher.teardown()
}
}複製代碼
OK 萬事具有,全部的代碼已經寫完,完整代碼以下:
/*----------------------------------------Dep---------------------------------------*/
//標識當前的Dep id
let uidep = 0
class Dep{
constructor () {
this.id = uidep++
// 存放全部的監聽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 = null
const targetStack = []
// 爲Dep.target 賦值
function pushTarget (Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = Watcher
}
function popTarget () {
Dep.target = targetStack.pop()
}
/*----------------------------------------Watcher------------------------------------*/
//去重 防止重複收集
let uid = 0
class Watcher{
constructor(vm,expOrFn,cb,options){
//傳進來的對象 例如Vue
this.vm = vm
if (options) {
this.deep = !!options.deep
this.user = !!options.user
}else{
this.deep = this.user = false
}
//在Vue中cb是更新視圖的核心,調用diff並更新視圖的過程
this.cb = cb
this.id = ++uid
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
if (typeof expOrFn === 'function') {
//data依賴收集走此處
this.getter = expOrFn
} else {
//watch依賴走此處
this.getter = this.parsePath(expOrFn)
}
//設置Dep.target的值,依賴收集時的watcher對象
this.value =this.get()
}
get(){
//設置Dep.target值,用以依賴收集
pushTarget(this)
const vm = this.vm
//此處會進行依賴收集 會調用data數據的 get
let value = this.getter.call(vm, vm)
//深度監聽
if (this.deep) {
traverse(value)
}
popTarget()
return value
}
//添加依賴
addDep (dep) {
//去重
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
//收集watcher 每次data數據 set
//時會遍歷收集的watcher依賴進行相應視圖更新或執行watch監聽函數等操做
dep.addSub(this)
}
}
}
//更新
update () {
this.run()
}
//更新視圖
run(){
console.log(`這裏會去執行Vue的diff相關方法,進而更新數據`)
const value = this.get()
const oldValue = this.value
this.value = value
if (this.user) {
//watch 監聽走此處
this.cb.call(this.vm, value, oldValue)
}else{
//data 監聽走此處
}
}
// 此方法得到每一個watch中key在data中對應的value值
//使用split('.')是爲了獲得 像'a.b.c' 這樣的監聽值
parsePath (path){
const bailRE = /[^w.$]/
if (bailRE.test(path)) return
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
//此處爲了兼容個人代碼作了一點修改
//此處使用新得到的值覆蓋傳入的值 所以可以處理 'a.b.c'這樣的監聽方式
if(i==0){
obj = obj.data[segments[i]]
}else{
obj = obj[segments[i]]
}
}
return obj
}
}
}
//深度監聽相關代碼 爲了兼容有一小點改動
const seenObjects = new Set()
function traverse (val) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse (val, seen) {
let i, keys
const isA = Array.isArray(val)
if (!isA && Object.prototype.toString.call(val)!= '[object Object]') return;
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--){
if(i == '__ob__') return;
_traverse(val[i], seen)
}
} else {
keys = Object.keys(val)
i = keys.length
while (i--){
if(keys[i] == '__ob__') return;
_traverse(val[keys[i]], seen)
}
}
}
/*----------------------------------------Observer------------------------------------*/
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)) {
//這裏只測試對象
} 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]])
}
}
}
//數據重複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()
}
}
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
})
}
/*----------------------------------------初始化watch------------------------------------*/
class stateWatch{
constructor (vm, watch) {
this.vm = vm
//初始化watch
this.initWatch(vm, watch)
}
initWatch (vm, watch) {
//遍歷watch對象
for (const key in watch) {
const handler = watch[key]
//數組則遍歷進行createWatcher
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
this.createWatcher(vm, key, handler[i])
}
} else {
this.createWatcher(vm, key, handler)
}
}
}
createWatcher (vm, key, handler) {
let options
if (Object.prototype.toString.call(handler) == '[object Object]' ) {
//處理對象
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
vm.$watch(key, handler, options)
}
}
/*----------------------------------------Vue------------------------------------*/
function Vue(){
}
Vue.prototype.$watch=function(expOrFn,cb,options){
const vm = this
options = options || {}
//此參數用於給data重新賦值時走watch的監聽函數
options.user = true
//watch依賴收集的Watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
//immediate=true時 會調用一次 watcher.run 方法,所以會調用一次watch中相關key的函數
if (options.immediate) {
cb.call(vm, watcher.value)
}
//返回一個取消監聽的函數
return function unwatchFn () {
watcher.teardown()
}
}複製代碼
再回頭看看上面那張簡單的Vue watch流程圖,測試代碼咱們嚴格按照流程圖順序進行
爲了方便觀看此處複製一份流程圖:
let vue = new Vue()複製代碼
定義一個data值並掛載到vue中,並給vue新增一個doSomething的方法:
let data={
name:'zane',
blog:'https://blog.seosiwei.com/',
age:20,
fn:'',
some:{
f:'xiaowang'
}
}
vue.data = data
vue.doSomething=()=>{
console.log(`i will do something`)
}複製代碼
定義一個watch值
let watch={
name: function (val, oldVal) {
console.log('----------name--------')
console.log('new: %s, old: %s', val, oldVal)
},
blog:function (val, oldVal) {
console.log('----------blog---------')
console.log('new: %s, old: %s', val, oldVal)
},
age:'doSomething',
fn:[
function handle1 (val, oldVal) { console.log('111111') },
function handle2 (val, oldVal) { console.log('222222') }
],
some:{
handler: function (val, oldVal) {
console.log('----------some---------')
console.log('new: %s, old: %s', val, oldVal)
},
immediate: true
},
'some.f': function (val, oldVal) {
console.log(`----some.f-----`)
console.log('new: %s, old: %s', val, oldVal)
},
}複製代碼
let updateComponent = (vm)=>{
// 收集依賴
data.age
data.blog
data.name
data.some
data.some.f
data.fn
}
new Watcher(vue,updateComponent)複製代碼
observe(data)
//此處會調用上面的函數updateComponent,從而調用 get 收集依賴複製代碼
new stateWatch(vue, watch)複製代碼
全部依賴都已經收集好是時候觸發了
//首先會當即調用一次watch中的some的函數
//會觸發vue下的doSomething方法
data.age=25
//會觸發watch中監聽的blog的函數
data.blog='http://www.seosiwei.com/'
//會觸發watch中監聽的name的函數
data.name='xiaozhang'
//會觸發watch中some.f監聽的函數
data.some.f='deep f'
//會觸發watch中fn監聽的兩個函數
data.fn='go fn'複製代碼
let data={
name:'zane',
blog:'https://blog.seosiwei.com/',
age:20,
fn:'',
some:{
f:'xiaowang'
}
}
let watch={
name: function (val, oldVal) {
console.log('----------name--------')
console.log('new: %s, old: %s', val, oldVal)
},
blog:function (val, oldVal) {
console.log('----------blog---------')
console.log('new: %s, old: %s', val, oldVal)
},
age:'doSomething',
fn:[
function handle1 (val, oldVal) { console.log('111111') },
function handle2 (val, oldVal) { console.log('222222') }
],
some:{
handler: function (val, oldVal) {
console.log('----------some---------')
console.log('new: %s, old: %s', val, oldVal)
},
immediate: true
},
'some.f': function (val, oldVal) {
console.log(`----some.f-----`)
console.log('new: %s, old: %s', val, oldVal)
},
}
let vue = new Vue()
vue.data = data
vue.doSomething=()=>{
console.log(`i will do something`)
}
let updateComponent = (vm)=>{
// 收集依賴
data.age
data.blog
data.name
data.some
data.some.f
data.fn
}
new Watcher(vue,updateComponent)
observe(data)
new stateWatch(vue, watch)複製代碼
watch實現完畢。