文章目录

  • 前言
  • 一、由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})为数据对象设置getset方法, 在初始化的时候会触发get, 后期更新数据会触发set.

get即初始化时检查Dep.target, 这个变量就是订阅该Dep一个Watcher, new Watcher()时会触发Watcher类的get()执行deppushTarget(this), 这个this指向一个Watcher, 随后pushTarget()Dep.target = 这个Watcher, 从而检索成功放行.

放行后执行dep.depend()调用该depdepend()方法进而将这个dep加入到Dep.Target对应的WatchernewDeps数组内, 表示这是该Watcher订阅的Dep, 同时Dep也会调用addSub()将这个Watcher加入自己的Subs数组表示这是自己的订阅者.
每个属性对应一个负责劫持的Observer, 每个Observer通知一个Dep, 一个Dep可以由多个Watcher订阅, Dep也通知变化到多个Watcher.

数据更新时会触发set进而调用Depnotify()通知函数, 通知订阅该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实例调用Depdepend(), 在depend()会调用Watcher类的addDep()方法, Watcher借此拿到Dep.
addDep还调用DepaddSub(), Dep借此拿到Watcher, 此时双方订阅与被订阅关系构成.
DepSubs数组用来存放所有订阅者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中)传自己过去, 然后pushTargetWatcher赋值给Dep.target, 此时defineReactive()里的dep.depend()能够执行.
直到newDep()发生, Depdepend(this)执行Dep.target.addDep()(addDep()Watcher的方法, 两者借此建立联系), 双方各自把对方加进自己的数组newDepSub, 订阅算是完成.

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, 执行Watcherevaluate()调用Watcherget()和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调用Depdepend(), 都会进行双向数据收集, 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与订阅相关推荐

  1. loraserver 源码解析 (四) lora-gateway-bridge

    lora-gateway-bridge  负责接收 gateway 通过 udp 发送的 packet-forwarder 数据 然后通过 MQTT broker 将报文转发给 LoRa Server ...

  2. new vue 方法参数_vue源码解析 lt;1gt; 数据驱动

    intro Vue.js 一个核心思想是数据驱动.所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据. 本文将介绍vue如何将模板和数据渲染成最终的 D ...

  3. Tomcat源码解析四:Tomcat关闭过程

    我们在Tomcat启动过程(Tomcat源代码阅读系列之三)一文中已经知道Tomcat启动以后,会启动6条线程,他们分别如下: [java] view plaincopy "ajp-bio- ...

  4. arcengine遍历属性表_Redis源码解析四--跳跃表

    Redis 跳跃表(skiplist) 1. 跳跃表(skiplist)介绍 定义:跳跃表是一个有序链表,其中每个节点包含不定数量的链接,节点中的第i个链接构成的单向链表跳过含有少于i个链接的节点. ...

  5. kafka-go源码解析四(Writer)

    概要 kafka-go区分同步写与异步写.同步写能严格确保写入的顺序,因为在写成功之前它会block住应用程序,同时返回错误信息.有三种控制写入完成的时机,1是消息发送完成即返回,2是leader收到 ...

  6. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  7. 《React源码解析》系列完结!

    前言 距离第一篇<React源码解析(一)>已经过去将近4个月的时间,由于是我第一次进行源码解析相关的写作,思路和文笔还不够成熟.一百多天以来,我基于读者反馈反思这几篇文章中的不足,同时也 ...

  8. webpack源码解析七(optimization)

    前言 前面我们写了几篇文章用来介绍webpack源码,跟着官网结合demo把整个webpack配置撸了一遍: webpack源码解析一 webpack源码解析二(html-webpack-plugin ...

  9. VINS-Mono-IMU预积分原理及源码解析

    IMU预积分原理 PVQ增量预积分(可理解为EKF过程中的状态估计) PVQ增量的连续形式 参照VINS-Mono论文知识点精炼(翻译)中IMU预积分章节,可知PVQ增量连续时间积分公式如下: α b ...

  10. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者"tingsking18"和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消 ...

最新文章

  1. boost::movelib::default_delete相关用法的测试程序
  2. windows程序快速启动的方式:WIN键+R
  3. linux创建数据库并设置密码,CentOS 8安装MySQL教程并创建数据库并添加用户
  4. postman测试工具中的js代码中的sendRequest()使用详解
  5. 北理工在线作业计算机的主要特点是( ),北理工18秋《计算机组成原理》在线作业【答案】...
  6. 中国光伏产业将面临何种形势?
  7. 银河麒麟 安卓nginx_银河麒麟Kydroid 2.0全新发布:原生支持海量安卓APP
  8. 统计学习(一):数据的组织和表示
  9. 2018-10-10 在浏览器插件中读取JSON资源文件
  10. mysql之 mysql_config_editor/login-path 登录密码保护
  11. Linux目录结构、bash的基础命令学习
  12. python播放全网视频+打包成exe
  13. axf下不了 keil5jlink_keil无法生成axf文件之解决方法
  14. Excel单元格引用
  15. Java字符串基础语法
  16. 惊!Go里面居然有这样精妙的小函数!
  17. python---flask解决跨域
  18. matlab冒号分号区别,matlab : 关于冒号 用法大全以及实例
  19. 客户信息的收集办法有哪些 如何进行客户信息管理
  20. 一款比XMIND更好用的思维导图

热门文章

  1. 杭州电子科技大学考研计算机科学与技术,杭州电子科技大学考研好考吗
  2. 元宇宙大杀器来了!小扎祭出4款VR头显,挑战视觉图灵测试
  3. 一亿融资在一家芯片初创公司可以烧多久?
  4. android 360度环拍,Android 4.2系统360度全景图拍摄试玩
  5. r语言中形成的c函数,R语言_par()函数用法
  6. AI根据代码内容可自动起函数名
  7. SAP 查询物料的非限制库存、质检库存、冻结库存
  8. 网课python程序设计答案_中国大学MOOCPython程序设计网课答案
  9. OFD转PDF格式免费在线转换
  10. Premiere 添加字幕