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 相关文章推荐
JS 密码强度验证(兼容IE,火狐,谷歌)
Mar 15 Javascript
下拉列表选择项的选中在不同浏览器中的兼容性问题探讨
Sep 18 Javascript
javascript获得网页窗口实际大小的示例代码
Sep 21 Javascript
js实现仿MSN带关闭功能的右下角弹窗代码
Sep 04 Javascript
Bootstrap幻灯片轮播图支持触屏左右手势滑动的实现方法
Oct 13 Javascript
详解jQuery中基本的动画方法
Dec 14 Javascript
Nuxt.js之自动路由原理的实现方法
Nov 21 Javascript
浅谈Node框架接入ELK实践总结
Feb 22 Javascript
Vue-cli3简单使用(图文步骤)
Apr 30 Javascript
原生js实现无缝轮播图
Jan 11 Javascript
jQuery实现点击滚动到指定元素上的方法分析
Mar 19 jQuery
如何解决vue在ios微信"复制链接"功能问题
Mar 26 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缓存技术介绍
2006/11/25 PHP
再次研究下cache_lite
2007/02/14 PHP
一个可以随意添加多个序列的tag函数
2009/07/21 Javascript
jquery.AutoComplete.js中文修正版(支持firefox)
2010/04/09 Javascript
js特效,页面下雪的小例子
2013/06/17 Javascript
showModelDialog弹出文件下载窗口的使用示例
2013/11/19 Javascript
基于Turn.js 实现翻书效果实例解析
2016/06/20 Javascript
JavaScript兼容浏览器FF/IE技巧
2016/08/14 Javascript
jQuery实现的导航下拉菜单效果示例
2016/09/05 Javascript
javascript学习笔记_浅谈基础语法,类型,变量
2016/09/19 Javascript
详解JavaScript的闭包、IIFE、apply、函数与对象
2016/12/21 Javascript
jQueryeasyui 中如何使用datetimebox 取两个日期间相隔的天数
2017/06/13 jQuery
JS一个简单的注册页面实例
2017/09/05 Javascript
基于js中style.width与offsetWidth的区别(详解)
2017/11/12 Javascript
vue通过点击事件读取音频文件的方法
2018/05/30 Javascript
Vue开发实现吸顶效果的示例代码
2018/08/21 Javascript
微信小程序实现拖拽功能
2019/09/26 Javascript
vue.js+ElementUI实现进度条提示密码强度效果
2020/01/18 Javascript
Vue仿Bibibili首页的问题
2021/01/21 Vue.js
使用基于Python的Tornado框架的HTTP客户端的教程
2015/04/24 Python
Python3.2模拟实现webqq登录
2016/02/15 Python
python strip() 函数和 split() 函数的详解及实例
2017/02/03 Python
Python机器学习logistic回归代码解析
2018/01/17 Python
Python二叉树定义与遍历方法实例分析
2018/05/25 Python
python读取并写入mat文件的方法
2019/07/12 Python
django框架使用views.py的函数对表进行增删改查内容操作详解【models.py中表的创建、views.py中函数的使用,基于对象的跨表查询】
2019/12/12 Python
美国知名的时尚购物网站:Anthropologie
2016/12/22 全球购物
若通过ObjectOutputStream向一个文件中多次以追加方式写入object,为什么用ObjectInputStream读取这些object时会产生StreamCorruptedException?
2016/10/17 面试题
Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
2014/07/27 面试题
护士辞职信范文
2014/01/19 职场文书
司法工作人员群众路线对照检查材料思想汇报
2014/09/30 职场文书
三方股份合作协议书
2014/10/13 职场文书
表扬通报怎么写
2015/01/16 职场文书
党员转正申请报告
2015/05/15 职场文书
如何书写公司员工保密协议?
2019/06/27 职场文书
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
2022/04/11 Python