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 打印内容方法小结
Nov 04 Javascript
js中同步与异步处理的方法和区别总结
Dec 25 Javascript
javascript针对cookie的基本操作实例详解
Nov 30 Javascript
jQuery中hover与mouseover和mouseout的区别分析
Dec 24 Javascript
JavaScript中的原始值和复杂值
Jan 07 Javascript
JS遍历数组和对象的区别及递归遍历对象、数组、属性的方法详解
Jun 14 Javascript
完美解决IE9浏览器出现的对象未定义问题
Sep 29 Javascript
常用Javascript函数与原型功能收藏(必看篇)
Oct 09 Javascript
vue-router路由懒加载和权限控制详解
Dec 13 Javascript
性能优化篇之Webpack构建速度优化的建议
Apr 03 Javascript
记录vue做微信自定义分享的一些问题
Sep 12 Javascript
Vue-cli打包后如何本地查看的操作
Sep 02 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正则替换处理HTML页面的方法
2015/06/17 PHP
php基于Fleaphp框架实现cvs数据导入MySQL的方法
2016/02/23 PHP
深入讲解PHP的对象注入(Object Injection)
2017/03/01 PHP
JQuery 初体验(建议学习jquery)
2009/04/25 Javascript
Mootools 1.2教程 Tooltips
2009/09/15 Javascript
Jquery中扩展方法extend使用技巧
2014/08/24 Javascript
jQuery插件slick实现响应式移动端幻灯片图片切换特效
2015/04/12 Javascript
基于jQuery倾斜打开侧边栏菜单特效代码
2015/09/15 Javascript
分享几种比较简单实用的JavaScript tabel切换
2015/12/31 Javascript
JQuery日期插件datepicker的使用方法
2016/03/03 Javascript
Angular.JS学习之依赖注入$injector详析
2016/10/20 Javascript
Django1.7+JQuery+Ajax验证用户注册集成小例子
2017/04/08 jQuery
ionic+AngularJs实现获取验证码倒计时按钮
2017/04/22 Javascript
js模块加载方式浅析
2017/08/12 Javascript
element-ui组件table实现自定义筛选功能的示例代码
2019/03/15 Javascript
js实现带积分弹球小游戏
2020/07/21 Javascript
[49:08]Secret vs VP 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
举例区分Python中的浅复制与深复制
2015/07/02 Python
Python整型运算之布尔型、标准整型、长整型操作示例
2017/07/21 Python
Python字符串格式化%s%d%f详解
2018/02/02 Python
python实现requests发送/上传多个文件的示例
2018/06/04 Python
python networkx 包绘制复杂网络关系图的实现
2019/07/10 Python
Python 使用 prettytable 库打印表格美化输出功能
2019/12/26 Python
pytorch 实现cross entropy损失函数计算方式
2020/01/02 Python
浅谈TensorFlow之稀疏张量表示
2020/06/30 Python
分享unittest单元测试框架中几种常用的用例加载方法
2020/12/02 Python
利用Python函数实现一个万历表完整示例
2021/01/23 Python
电子技术专业中专生的自我评价
2013/12/17 职场文书
求职自荐信怎么写
2014/03/06 职场文书
治庸问责心得体会
2014/09/12 职场文书
基层党员群众路线教育实践活动个人对照检查材料思想汇报
2014/10/05 职场文书
群众路线四风对照检查材料
2014/11/04 职场文书
长城英文导游词
2015/01/30 职场文书
优秀创业计划书分享
2019/07/19 职场文书
学习师德师风的心得体会(2篇)
2019/10/08 职场文书
深入浅析Redis 集群伸缩原理
2021/05/15 Redis