欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue.js原理分析之observer模块详解

程序员文章站 2022-04-08 15:40:14
介绍 observer是vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。 注意:本文是针对vue@2.1.8进...

介绍

observer是vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。

注意:本文是针对vue@2.1.8进行分析

observer模块在vue项目中的代码位置是src/core/observer,模块共分为这几个部分:

  1. observer: 数据的观察者,让数据对象的读写操作都处于自己的监管之下
  2. watcher: 数据的订阅者,数据的变化会通知到watcher,然后由watcher进行相应的操作,例如更新视图
  3. dep: observer与watcher的纽带,当数据变化时,会被observer观察到,然后由dep通知到watcher

示意图如下:

Vue.js原理分析之observer模块详解

observer

observer类定义在src/core/observer/index.js中,先来看一下observer的构造函数

constructor (value: any) {
 this.value = value
 this.dep = new dep()
 this.vmcount = 0
 def(value, '__ob__', this)
 if (array.isarray(value)) {
 const augment = hasproto
 ? protoaugment
 : copyaugment
 augment(value, arraymethods, arraykeys)
 this.observearray(value)
 } else {
 this.walk(value)
 }
}

value是需要被观察的数据对象,在构造函数中,会给value增加__ob__属性,作为数据已经被observer观察的标志。如果value是数组,就使用observearray遍历value,对value中每一个元素调用observe分别进行观察。如果value是对象,则使用walk遍历value上每个key,对每个key调用definereactive来获得该key的set/get控制权。

解释下上面用到的几个函数的功能:

  • observearray: 遍历数组,对数组的每个元素调用observe
  • observe: 检查对象上是否有__ob__属性,如果存在,则表明该对象已经处于observer的观察中,如果不存在,则new observer来观察对象(其实还有一些判断逻辑,为了便于理解就不赘述了)
  • walk: 遍历对象的每个key,对对象上每个key的数据调用definereactive
  • definereactive: 通过object.defineproperty设置对象的key属性,使得能够捕获到该属性值的set/get动作。一般是由watcher的实例对象进行get操作,此时watcher的实例对象将被自动添加到dep实例的依赖数组中,在外部操作触发了set时,将通过dep实例的notify来通知所有依赖的watcher进行更新。

如果不太理解上面的文字描述可以看一下图:

Vue.js原理分析之observer模块详解

dep

dep是observer与watcher之间的纽带,也可以认为dep是服务于observer的订阅系统。watcher订阅某个observer的dep,当observer观察的数据发生变化时,通过dep通知各个已经订阅的watcher。

dep提供了几个接口:

  • addsub: 接收的参数为watcher实例,并把watcher实例存入记录依赖的数组中
  • removesub: 与addsub对应,作用是将watcher实例从记录依赖的数组中移除
  • depend: dep.target上存放这当前需要操作的watcher实例,调用depend会调用该watcher实例的adddep方法,adddep的功能可以看下面对watcher的介绍
  • notify: 通知依赖数组中所有的watcher进行更新操作

watcher

watcher是用来订阅数据的变化的并执行相应操作(例如更新视图)的。watcher的构造器函数定义如下:

constructor (vm, exporfn, cb, options) {
 this.vm = vm
 vm._watchers.push(this)
 // options
 if (options) {
 this.deep = !!options.deep
 this.user = !!options.user
 this.lazy = !!options.lazy
 this.sync = !!options.sync
 } else {
 this.deep = this.user = this.lazy = this.sync = false
 }
 this.cb = cb
 this.id = ++uid // uid for batching
 this.active = true
 this.dirty = this.lazy // for lazy watchers
 this.deps = []
 this.newdeps = []
 this.depids = new set()
 this.newdepids = new set()
 this.expression = process.env.node_env !== 'production'
 ? exporfn.tostring()
 : ''
 if (typeof exporfn === 'function') {
 this.getter = exporfn
 } else {
 this.getter = parsepath(exporfn)
 if (!this.getter) {
 this.getter = function () {}
 process.env.node_env !== 'production' && warn(
 `failed watching path: "${exporfn}" ` +
 'watcher only accepts simple dot-delimited paths. ' +
 'for full control, use a function instead.',
 vm
 )
 }
 }
 this.value = this.lazy
 ? undefined
 : this.get()
}

参数中,vm表示组件实例,exporfn表示要订阅的数据字段(字符串表示,例如a.b.c)或是一个要执行的函数,cb表示watcher运行后的回调函数,options是选项对象,包含deep、user、lazy等配置。

watcher实例上有这些方法:

  • get: 将dep.target设置为当前watcher实例,在内部调用this.getter,如果此时某个被observer观察的数据对象被取值了,那么当前watcher实例将会自动订阅数据对象的dep实例
  • adddep: 接收参数dep(dep实例),让当前watcher订阅dep
  • cleanupdeps: 清除newdepids和newdep上记录的对dep的订阅信息
  • update: 立刻运行watcher或者将watcher加入队列中等待统一flush
  • run: 运行watcher,调用this.get()求值,然后触发回调
  • evaluate: 调用this.get()求值
  • depend: 遍历this.deps,让当前watcher实例订阅所有dep
  • teardown: 去除当前watcher实例所有的订阅

array methods

在src/core/observer/array.js中,vue框架对数组的push、pop、shift、unshift、sort、splice、reverse方法进行了改造,在调用数组的这些方法时,自动触发dep.notify(),解决了调用这些函数改变数组后无法触发更新的问题。

在vue的官方文档中对这个也有说明:http://cn.vuejs.org/v2/guide/list.html#变异方法

总结

以上就是这篇文中的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。