Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅
文章目录
- 前言
- 一、由initData整理思路
- 1.1 initData()到observe()
- 1.2 observe()
- 1.3 new Observer()
- 1.4 def()
- 1.5 walk()
- 1.6 defineReactive()
- 1.7 new Dep() && depend()
- 1.7.1 pushTarget()
- 1.8 Watcher
- 1.9 initData图示
- 二、由initComputed整理思路
- 2.1 由initComputed()到defineComputed()
- 2.2 defineComputed()
- 2.3 createComputedGetter()
- 2.4 initComputed图示
- 总结
前言
我还是感觉看了个寂寞, 一直分析代码执行, 但是忽略了比较宏观的一些事, 比如Dep
如何将变化通知到Watcher
, Watcher
是怎么订阅Dep
的, 缺乏这种意识.
一、由initData整理思路
1.1 initData()到observe()
从vm
提取data
数据对象, 传给observe
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}observe(data, true)
}
1.2 observe()
检查该data
数据对象是否已受观察, 即检查该数据对象内是否具备__ob__
属性, 若有则说明已受观察不做处理, 若无则设置观察者new Observer(value)
.
可能因为是初始化, 所以只对没有被观察的设置观察.
export function observe (value: any, asRootData: ?boolean): Observer | void {if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {ob = new Observer(value)}if (asRootData && ob) {ob.vmCount++}return ob
}
1.3 new Observer()
将Dep
, 数据对象, vmConunt
传入def()
export class Observer {value: any;dep: Dep;vmCount: number;constructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this) // 数据对象, Dep, vmCountthis.walk(value)walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}}
}
1.4 def()
核心defineProperty()
为数据对象value
设置__ob__
属性, 至此该对象被Dep劫持
Object.defineProperty(value, '__ob__', { Dep, 数据对象, vmCount })
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {Object.defineProperty(obj, key, {value: val,enumerable: !!enumerable,writable: true,configurable: true})
}
完成后value
上具备__ob__
属性.
// value
{xxx: 'xxx',x: 'xxx',__ob__: {dep: {...subs: [订阅者0,订阅者1,...]}}
}
此时Observer
又调用了walk(value)
1.5 walk()
def
完成后value
会带着Dep
来执行walk()
提取数据对象的key构成数组, 遍历数组对其内部元素defineReactive(数据对象, 属性名)
将数据对象中每个属性都执行defineReactive()
.
walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}
}
1.6 defineReactive()
defineReactive(数据对象, 属性名)
, 内部Object.property(数据对象, 属性名, {..., get, set})
为数据对象设置get
和set
方法, 在初始化的时候会触发get
, 后期更新数据会触发set
.
get
即初始化时检查Dep.target
, 这个变量就是订阅该Dep
的一个Watcher
, new Watcher()
时会触发Watcher
类的get()
执行dep
的pushTarget(this)
, 这个this
指向一个Watcher
, 随后pushTarget()
内Dep.target = 这个Watcher
, 从而检索成功放行.
放行后执行dep.depend()
调用该dep
的depend()
方法进而将这个dep
加入到Dep.Target对应的Watcher
的newDeps
数组内, 表示这是该Watcher
订阅的Dep
, 同时Dep
也会调用addSub()
将这个Watcher
加入自己的Subs
数组表示这是自己的订阅者.
每个属性对应一个负责劫持的Observer
, 每个Observer
通知一个Dep
, 一个Dep
可以由多个Watcher
订阅, Dep
也通知变化到多个Watcher
.
数据更新时会触发set
进而调用Dep
的notify()
通知函数, 通知订阅该Dep
的所有Watcher
执行update()
, 下面会说到.
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean
) {const dep = new Dep()Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {dep.depend()}return value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : valif (setter) {setter.call(obj, newVal)} else {val = newVal}dep.notify()}})}
1.7 new Dep() && depend()
defineReactive()
中defineProperty()
定义的get()
内部由Observer
实例调用Dep
的depend()
, 在depend()
会调用Watcher
类的addDep()
方法, Watcher
借此拿到Dep
.
addDep
还调用Dep
的addSub()
, Dep
借此拿到Watcher
, 此时双方订阅与被订阅关系构成.
Dep
的Subs
数组用来存放所有订阅者Watcher
.
export default class Dep {static target: ?Watcher;id: number;subs: Array<Watcher>;constructor () {this.id = uid++this.subs = []}addSub (sub: Watcher) {this.subs.push(sub)}depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {const subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}}
1.7.1 pushTarget()
Dep.target = null // 全局
const targetStack = [] // 全局export function pushTarget (target: ?Watcher) {targetStack.push(target)Dep.target = target
}
1.8 Watcher
Watcher
实例化先调用get()
里的pushTarget()
(这个函数也在dep.js中但不属于Dep中)传自己过去, 然后pushTarget
将Watcher
赋值给Dep.target
, 此时defineReactive()
里的dep.depend()
能够执行.
直到newDep()
发生, Dep
的depend(this)
执行Dep.target.addDep()
(addDep()
是Watcher
的方法, 两者借此建立联系), 双方各自把对方加进自己的数组newDep
和Sub
, 订阅算是完成.
export default class Watcher {get () {pushTarget(this) // dep里的pushTarget}addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {this.newDepIds.add(id)this.newDeps.push(dep) // Watcher拿到Depif (!this.depIds.has(id)) {dep.addSub(this) // Dep拿到Watcher}}}update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {queueWatcher(this)}}run () {if (this.active) {const value = this.get()}}}
1.9 initData图示
二、由initComputed整理思路
2.1 由initComputed()到defineComputed()
整个主体在一个loop完成, 为每个计算属性实例化Watcher
, vm._watchers.push(this)
将该Watcher存入vm的诸多Watcher
中.
然后对每个计算属性执行defineComputed(vm, 计算属性名, 计算属性值)
const sharedPropertyDefinition = { // 全局enumerable: true,configurable: true,get: noop,set: noop
}function initComputed (vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)const isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (!isSSR) {watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}if (!(key in vm)) {defineComputed(vm, key, userDef)}}
}
2.2 defineComputed()
针对computed
属性值的不同书写方式组成sharedPropertyDefinition
作为该计算属性的描述符参数.
export function defineComputed (target: any,key: string,userDef: Object | Function
) {const shouldCache = !isServerRendering()if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}Object.defineProperty(target, key, sharedPropertyDefinition)
}
2.3 createComputedGetter()
在上一步为每个计算属性实例化了Watcher
, 拿到当前计算属性的Watcher
, 执行Watcher
的evaluate()
调用Watcher
的get()
和Dep的pushTarget
, 导致Dep.target = 当前Watcher
Dep.target
存在后由Watcher
调用Dep的depend()
, 然后就回到1.8的addDep()
订阅了.
function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {if (watcher.dirty) {watcher.evaluate()}if (Dep.target) {watcher.depend()}return watcher.value}}
}
2.4 initComputed图示
总结
每次到addDep()
, 也就是每次Watcher
调用Dep
的depend()
, 都会进行双向数据收集, Watcher和Dep互相同步数据, Dep的数据更新后通知订阅自己的每个Watcher调用notify()
进而调用update()
更新数据, computed计算属性在接受
defineProperty()之后会拥有
set()和
get()方法, 更新调
set()之后
Dep`通知.
上一篇: Vue 2.6.13 源码解析(三) 初始化
下一篇: Vue 2.6.13 源码解析(五) 感知变化以发起通知
Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅相关推荐
- loraserver 源码解析 (四) lora-gateway-bridge
lora-gateway-bridge 负责接收 gateway 通过 udp 发送的 packet-forwarder 数据 然后通过 MQTT broker 将报文转发给 LoRa Server ...
- new vue 方法参数_vue源码解析 lt;1gt; 数据驱动
intro Vue.js 一个核心思想是数据驱动.所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据. 本文将介绍vue如何将模板和数据渲染成最终的 D ...
- Tomcat源码解析四:Tomcat关闭过程
我们在Tomcat启动过程(Tomcat源代码阅读系列之三)一文中已经知道Tomcat启动以后,会启动6条线程,他们分别如下: [java] view plaincopy "ajp-bio- ...
- arcengine遍历属性表_Redis源码解析四--跳跃表
Redis 跳跃表(skiplist) 1. 跳跃表(skiplist)介绍 定义:跳跃表是一个有序链表,其中每个节点包含不定数量的链接,节点中的第i个链接构成的单向链表跳过含有少于i个链接的节点. ...
- kafka-go源码解析四(Writer)
概要 kafka-go区分同步写与异步写.同步写能严格确保写入的顺序,因为在写成功之前它会block住应用程序,同时返回错误信息.有三种控制写入完成的时机,1是消息发送完成即返回,2是leader收到 ...
- 【Vue.js源码解析 一】-- 响应式原理
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...
- 《React源码解析》系列完结!
前言 距离第一篇<React源码解析(一)>已经过去将近4个月的时间,由于是我第一次进行源码解析相关的写作,思路和文笔还不够成熟.一百多天以来,我基于读者反馈反思这几篇文章中的不足,同时也 ...
- webpack源码解析七(optimization)
前言 前面我们写了几篇文章用来介绍webpack源码,跟着官网结合demo把整个webpack配置撸了一遍: webpack源码解析一 webpack源码解析二(html-webpack-plugin ...
- VINS-Mono-IMU预积分原理及源码解析
IMU预积分原理 PVQ增量预积分(可理解为EKF过程中的状态估计) PVQ增量的连续形式 参照VINS-Mono论文知识点精炼(翻译)中IMU预积分章节,可知PVQ增量连续时间积分公式如下: α b ...
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者"tingsking18"和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消 ...
最新文章
- boost::movelib::default_delete相关用法的测试程序
- windows程序快速启动的方式:WIN键+R
- linux创建数据库并设置密码,CentOS 8安装MySQL教程并创建数据库并添加用户
- postman测试工具中的js代码中的sendRequest()使用详解
- 北理工在线作业计算机的主要特点是( ),北理工18秋《计算机组成原理》在线作业【答案】...
- 中国光伏产业将面临何种形势?
- 银河麒麟 安卓nginx_银河麒麟Kydroid 2.0全新发布:原生支持海量安卓APP
- 统计学习(一):数据的组织和表示
- 2018-10-10 在浏览器插件中读取JSON资源文件
- mysql之 mysql_config_editor/login-path 登录密码保护
- Linux目录结构、bash的基础命令学习
- python播放全网视频+打包成exe
- axf下不了 keil5jlink_keil无法生成axf文件之解决方法
- Excel单元格引用
- Java字符串基础语法
- 惊!Go里面居然有这样精妙的小函数!
- python---flask解决跨域
- matlab冒号分号区别,matlab : 关于冒号 用法大全以及实例
- 客户信息的收集办法有哪些 如何进行客户信息管理
- 一款比XMIND更好用的思维导图
热门文章
- 杭州电子科技大学考研计算机科学与技术,杭州电子科技大学考研好考吗
- 元宇宙大杀器来了!小扎祭出4款VR头显,挑战视觉图灵测试
- 一亿融资在一家芯片初创公司可以烧多久?
- android 360度环拍,Android 4.2系统360度全景图拍摄试玩
- r语言中形成的c函数,R语言_par()函数用法
- AI根据代码内容可自动起函数名
- SAP 查询物料的非限制库存、质检库存、冻结库存
- 网课python程序设计答案_中国大学MOOCPython程序设计网课答案
- OFD转PDF格式免费在线转换
- Premiere 添加字幕