vm.$watch

用法: vm.$watch( expOrFn, callback, [options] ),返回值为unwatch是一个函数用来取消观察;下面主要理解options中的两个参数deepimmediate以及unwatch

Vue.prototype.$watch = function (expOrFn, cb, options) {    const vm = this    options = options || {}    const watcher = new Watcher(vm, expOrFn, cb, options)      if(options.immediate) {        cb.call(vm, watcher,.value)    }    return function unwatchFn() {        watcher.teardown()    }}

immediate

从上面代码中可以看出当immediatetrue时,就会直接进行执行回调函数

unwatch

实现方式是:

  1. 将被访问到的数据dep收集到watchs实例对象上,通过this.deps存起来
  2. 将被访问到的数据dep.id收集到watchs实例对象上,通过this.depIds存起来
  3. 最后通过watchs实例对象的teardown进行删除
class Watcher {    constructor (vm, expOrFn, cb) {        this.vm = vm        this.deps = []        this.depIds = new Set()        if(typeof expOrFn === 'function') {            this.getter = expOrFn        }else {            this.getter = parsePath(expOrFn)        }        this.cb = cb        this.value = this.get()    }    ....    addDep (dep) {        const id = dep.id             //参数dep是Dep实例对象        if(!this.depIds.has(id)) {    //判断是否存在避免重复添加            this.depIds.add(id)                   this.deps.push(dep)            dep.addSub(this)         //this 是依赖        }    }    teardown () {        let i = this.deps.length        while (i--) {            this.deps[i].removeSub(this)        }    }}let uid = 0class Dep {    constructor () {        this.id = uid++        ...    }    ...    depend () {        if(window.target) {            window.target.addDep(this)    //将this即当前dep对象加入到watcher对象上        }    }    removeSub (sub) {        const index = this.subs.indexOf(sub)        if(index > -1) {            return this.subs.splice(index, 1)        }    }}

分析

当执行teardown() 时需要循环;因为例如expOrFn = function () { return this.name + this.age },这时会有两个dep分别是nameage分别都加入了watcher依赖(this),都会加入到this.deps中,所以需要循环将含有依赖的dep都删除其依赖

deep

需要明白的是

  1. deep干啥用的,例如data = {arr: [1, 2, {b: 6]},当我们只是监听data.arr时,在[1, 2, {b: 66}]这个数值内部发生变化时,也需要触发,即b = 888

怎么做呢?

class Watcher {    constructor (vm, expOrFn, cb, options) {        this.vm = vm        this.deps = []        this.depIds = new Set()        if(typeof expOrFn === 'function') {            this.getter = expOrFn        }else {            this.getter = parsePath(expOrFn)        }        if(options) {                    //取值            this.deep = !!options.deep        }else {            this.deep = false        }        this.cb = cb        this.value = this.get()    }    get () {        window.target = this        let value = this.getter.call(vm, vm)        if(this.deep) {            traverse(value)        }        window.target = undefined        return value    }    ...}const seenObjects = new Set()function traverse (val) {    _traverse(val, seenObjects)    seenObjects.clear()}function _traverse(val, seen) {    let i, keys    const isA = Array.isArray(val)    if((!isA && isObject(val)) || Object.isFrozen(val)) {  //判断val是否是对象或者数组以及是否被冻结        return    }    if(val._ob_) {        const depId = val._ob_.dep.id     //可以看前面一篇我们对Observer类添加了this.dep = new Dep(),所以能访问其dep.id        if(seen.has(depId)) {            return        }        seen.add(depId)    }    if(isA) {        i = val.length        while (i--) _traverse(val[i], seen)    } else {        keys = Object.keys(val)        i = keys.length        while (i--) _traverse(val[i], seen)    }}

分析

  1. window.target = this,寄存依赖
  2. let value = this.getter.call(vm, vm) 访问当前val,并执行get

dep.depend(),如果发现val为数组,则将依赖加入到observerdep中,也就实现了对当前数组的拦截

  1. traverse(value) 也就是执行_traverse(val, seenObjects);核心就是对被Observerval通过val[i]通过这种操作,间接触发get,将依赖添加到当前数值的dep中,这样也就实现了,当内部数据发生变化,也会循环subs执行依赖的update,从而触发回调;当是数组时,只需进行遍历,看内部是否有Object对象即可,因为在第二步的时候,会对val进行判断是否是数组,变改变七个方法的value,在遍历;所以这边只要是内部数组都会进行拦截操作,添加依赖,即对象{}这种没没添加依赖。
  2. seenObjects.clear()当内部所以类型数据都添加好其依赖后,就清空。
  3. window.target = undefined消除依赖

vm.$set

用法: vm.$set(target, key, value)

作用

  1. 对于数组,进行set则是添加新元素,并需要触发依赖更新
  2. 对于对象,如果key值存在,则是修改value;不存在,则是添加新元素,需新元素要进行响应式处理,以及触发更新
  3. 对于对象本身不是响应式,则直接添加key-value,无需处理
Vue.prototype.$set = function (target, key, val) {   if(Array.isArray(target) && isValidArrayIndex(key)) {    //是数组并且key有效        target.length = Math.max(target.length, key)   //处理key > target.length        target.splice(key, 1, val)   //添加新元素,并输出依赖更新同时新元素也会进行`Obsever`处理        return val   }   if(key in targert && !(key in Object.prototype) {  //能遍历并且是自身key        target[key] = val   //触发set,执行依赖更新        return val   }   const ob = target._ob_   if(target.isVue || (ob && ob.vm.Count) {  //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象)       //触发警告       return   }   if(!ob) {    //只添加       target[key] = val       return val   }   defineReactive(ob.value, key, val)  //进行响应式处理   ob.dep.notify()  //触发依赖更新   returnv val}

vm.$delete

用法: vm.$delete( target, key)

作用

  1. 对于数组,进行delete则是删除新元素,并需要触发依赖更新
  2. 对于对象,如果key值不存在,直接return,存在,删除元素,
  3. 对于对象本身不是响应式,则只删除key-value,无需其他处理
Vue.prototype.$delete = function (target, key) {    if(Array.isArray(target) && isValidArrayIndex(key)) {        target.splice(key, 1)        return    }    const ob = target._ob_    if(target.isVue || (ob && ob.vm.Count) {  //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象)        //触发警告        return   }   if(!hasOwn(target, key)) {       return   }    delete target[key]    if(!ob) {        return    }    ob.dep.notify()}

掘金地址