Vue侦测相关api的实现方法

作者:Infinity 时间:2024-05-09 09:52:46 

vm.$watch

用法: vm.$watch( expOrFn, callback, [options] ) ,返回值为 unwatch 是一个函数用来取消观察;下面主要理解 options 中的两个参数 deep 和 immediate 以及 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

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

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 分别是 name 与 age 分别都加入了 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 为数组,则将依赖加入到 observer 的 dep 中,也就实现了对当前数组的拦截

  1. traverse(value) 也就是执行 _traverse(val, seenObjects) ;核心就是对被 Observer 的 val 通过 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()
}

来源:https://segmentfault.com/a/1190000019253948

标签:Vue,侦测,api
0
投稿

猜你喜欢

  • 解决MySql客户端秒退问题(找不到my.ini)

    2024-01-20 15:36:52
  • 利用mycat实现mysql数据库读写分离的示例

    2024-01-12 21:55:52
  • JS代码格式化和语法着色V2

    2023-07-02 05:18:27
  • Python抓包并解析json爬虫的完整实例代码

    2021-12-17 14:54:32
  • 源码解析python的内存回收机制

    2023-05-19 18:12:16
  • python 中的np.zeros()和np.ones()函数详解

    2023-01-14 10:23:58
  • MySql5.6使用validate password 插件加强密码强度的安装及使用方法

    2024-01-24 15:39:20
  • MySQL六种约束的示例详解

    2024-01-16 19:15:38
  • 用"表情符号"做植入广告 是否可行呢?

    2009-02-23 13:07:00
  • python+pytest接口自动化之token关联登录的实现

    2023-01-21 13:27:37
  • python通过pip更新所有已安装的包实现方法

    2021-06-04 03:22:34
  • vue3中关于路由hash与History的设置

    2024-05-13 09:14:24
  • SQLServer数据库密码短时间强制过期的解决

    2024-01-18 09:40:01
  • sql server 触发器实例代码

    2012-01-05 19:09:28
  • jupyter notebook中图片显示不出来的解决

    2023-12-16 20:44:54
  • mysql慢查询优化之从理论和实践说明limit的优点

    2024-01-26 11:59:51
  • 网页设计中怎么将px换成em

    2008-04-16 13:50:00
  • python从入门到精通(DAY 3)

    2023-11-03 08:23:18
  • Python画图工具Matplotlib库常用命令简述

    2021-10-11 07:28:07
  • python读文件逐行处理的示例代码分享

    2023-03-17 03:54:04
  • asp之家 网络编程 m.aspxhome.com