Vue侦测相关api的实现方法


Posted in Javascript onMay 22, 2019

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()
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
使用javascript为网页增加夜间模式
Jan 26 Javascript
javascript中的括号()用法小结
Apr 14 Javascript
JavaScript中对象介绍
Dec 31 Javascript
折叠菜单及选择器的运用
Feb 03 Javascript
基于vue+ bootstrap实现图片上传图片展示功能
May 17 Javascript
JS使用ActiveXObject实现用户提交表单时屏蔽敏感词功能
Jun 20 Javascript
十个免费的web前端开发工具详细整理
Sep 18 Javascript
vue实现图书管理demo详解
Oct 17 Javascript
详解webpack4多入口、多页面项目构建案例
May 25 Javascript
Vue表单demo v-model双向绑定问题
Jun 29 Javascript
JavaScript闭包与作用域链实例分析
Jan 21 Javascript
JS 4个超级实用的小技巧 提升开发效率
Oct 05 Javascript
一文快速详解前端框架 Vue 最强大的功能
May 21 #Javascript
微信小程序系列之自定义顶部导航功能
May 21 #Javascript
js计算两个时间差 天 时 分 秒 毫秒的代码
May 21 #Javascript
微信小程序websocket实现即时聊天功能
May 21 #Javascript
Node.JS在命令行中检查Chrome浏览器是否安装并打开指定网址
May 21 #Javascript
taro开发微信小程序的实践
May 21 #Javascript
element-ui表格合并span-method的实现方法
May 21 #Javascript
You might like
php中return的用法实例分析
2015/02/28 PHP
9个javascript语法高亮插件 推荐
2009/07/18 Javascript
用jquery ajax获取网站Alexa排名的代码
2009/12/12 Javascript
基于jQuery试卷自动排版系统
2010/07/18 Javascript
html中的input标签的checked属性jquery判断代码
2012/09/19 Javascript
js 3种归并操作的实例代码
2013/10/30 Javascript
全系IE支持Bootstrap的解决方法
2015/10/19 Javascript
AngularJS中如何使用$http对MongoLab数据表进行增删改查
2016/01/23 Javascript
Jquery on方法绑定事件后执行多次的解决方法
2016/06/02 Javascript
bootstrap table表格插件使用详解
2017/05/08 Javascript
JS实现延迟隐藏功能的方法(类似QQ头像鼠标放上展示信息)
2017/12/28 Javascript
vue.js获得当前元素的文字信息方法
2018/03/09 Javascript
layui监听select变化,以及设置radio选中的方法
2019/09/24 Javascript
js获取本日、本周、本月的时间代码
2020/02/01 Javascript
js+canvas实现转盘效果(两个版本)
2020/09/13 Javascript
python使用webbrowser浏览指定url的方法
2015/04/04 Python
Python爬取智联招聘数据分析师岗位相关信息的方法
2019/08/13 Python
使用opencv中匹配点对的坐标提取方式
2020/06/04 Python
利用python中的matplotlib打印混淆矩阵实例
2020/06/16 Python
Python绘图之柱形图绘制详解
2020/07/28 Python
Python QT组件库qtwidgets的使用
2020/11/02 Python
OpenCV+Python3.5 简易手势识别的实现
2020/12/21 Python
HTML5新表单元素_动力节点Java学院整理
2017/07/12 HTML / CSS
原装进口全世界:天猫国际
2016/08/03 全球购物
美国在线眼镜商城:Eyeglasses.com
2017/06/26 全球购物
会计大学生职业生涯规划书范文
2014/01/13 职场文书
员工生日活动方案
2014/08/24 职场文书
学生吸烟检讨书
2014/09/14 职场文书
离婚财产处理协议书
2014/09/30 职场文书
2015毕业实习推荐信
2015/03/23 职场文书
涨价通知怎么写
2015/04/23 职场文书
2015年监理个人工作总结
2015/05/23 职场文书
2015年学生资助工作总结
2015/05/25 职场文书
小学生禁毒教育心得体会
2016/01/15 职场文书
Redis Cluster 字段模糊匹配及删除
2021/05/27 Redis
Python socket如何解析HTTP请求内容
2022/02/12 Python