Vue侦测相关api

栏目: JavaScript · 发布时间: 6年前

内容简介:用法:从上面代码中可以看出当当执行

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 = 0
class 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()
}

掘金地址


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning ASP.NET 4 in C# and Vb

Beginning ASP.NET 4 in C# and Vb

Imar Spaanjaars / Wrox / 2010-3-19 / GBP 29.99

This book is for anyone who wants to learn how to build rich and interactive web sites that run on the Microsoft platform. With the knowledge you gain from this book, you create a great foundation to ......一起来看看 《Beginning ASP.NET 4 in C# and Vb》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器