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 轻松支持函数重载 (Part 2 - 实现)
Aug 04 Javascript
被jQuery折腾得半死,揭秘为何jQuery为何在IE/Firefox下均无法使用
Jan 22 Javascript
jQuery选择器中含有空格的使用示例及注意事项
Aug 25 Javascript
jQuery实现的仿select功能代码
Aug 19 Javascript
基于javascript实现tab选项卡切换特效调试笔记
Mar 30 Javascript
基于JQuery的$.ajax方法进行异步请求导致页面闪烁的解决办法
May 10 Javascript
省市二级联动小案例讲解
Jul 24 Javascript
微信小程序中子页面向父页面传值实例详解
Mar 20 Javascript
bootstrap中selectpicker下拉框使用方法实例
Mar 22 Javascript
JavaScript实现读取与输出XML文件数据的方法示例
Jun 05 Javascript
JavaScript循环遍历你会用哪些之小结篇
Sep 28 Javascript
vue之debounce属性被移除及处理详解
Nov 13 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
从零开始的异世界生活:第二季延期后,B站上架了第二部剧场版
2020/05/06 日漫
PHP中改变图片的尺寸大小的代码
2011/07/17 PHP
PHP中register_globals参数为OFF和ON的区别(register_globals 使用详解)
2012/02/05 PHP
php+js实现百度地图多点标注的方法
2016/11/30 PHP
Yii2.0框架behaviors方法使用实例分析
2019/09/30 PHP
把input初始值不写value的具体实现方法
2013/07/04 Javascript
Chrome扩展页面动态绑定JS事件提示错误
2014/02/11 Javascript
javascript中的五种基本数据类型
2015/08/26 Javascript
js简单倒计时实现代码
2016/04/30 Javascript
JavaScript绑定事件监听函数的通用方法
2016/05/14 Javascript
jquery自适应布局的简单实例
2016/05/28 Javascript
jQuery如何防止Ajax重复提交
2016/10/14 Javascript
Node.js利用debug模块打印出调试日志的方法
2017/04/25 Javascript
微信小程序 wx.request方法的异步封装实例详解
2017/05/18 Javascript
vue实现tab切换外加样式切换方法
2018/03/16 Javascript
使用vue根据状态添加列表数据和删除列表数据的实例
2018/09/29 Javascript
JS中的算法与数据结构之集合(Set)实例详解
2019/08/20 Javascript
javascript写一个ajax自动拦截并下载数据代码实例
2019/09/07 Javascript
Vue的transition-group与Virtual Dom Diff算法的使用
2019/12/09 Javascript
python实现的udp协议Server和Client代码实例
2014/06/04 Python
Python基于动态规划算法解决01背包问题实例
2017/12/06 Python
一篇文章快速了解Python的GIL
2018/01/12 Python
Python实现的矩阵转置与矩阵相乘运算示例
2019/03/26 Python
python之列表推导式的用法
2019/11/29 Python
Tensorflow矩阵运算实例(矩阵相乘,点乘,行/列累加)
2020/02/05 Python
解决Alexnet训练模型在每个epoch中准确率和loss都会一升一降问题
2020/06/17 Python
使用html5 canvas 画时钟代码实例分享
2015/11/11 HTML / CSS
Ticketmaster意大利:音乐会、节日、艺术和剧院的官方门票
2019/12/23 全球购物
学生周末长期请假条
2014/02/15 职场文书
2014年教研活动总结范文
2014/04/26 职场文书
亲子活动总结
2014/04/26 职场文书
化工操作工岗位职责
2014/04/29 职场文书
药品销售内勤岗位职责
2015/04/13 职场文书
《悬崖边的树》读后感2篇
2019/12/02 职场文书
SQL试题 使用窗口函数选出连续3天登录的用户
2022/04/24 Oracle
Spring Boot项目如何优雅实现Excel导入与导出功能
2022/06/10 Java/Android