IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅 -> 正文阅读

[JavaScript知识库]Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅


前言

我还是感觉看了个寂寞, 一直分析代码执行, 但是忽略了比较宏观的一些事, 比如Dep如何将变化通知到Watcher, Watcher是怎么订阅Dep的, 缺乏这种意识.


一、由initData整理思路

1.1 initData()到observe()

vm提取data数据对象, 传给observe

function initData (vm: Component) {
  let data = vm.$options.data
  data = 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 = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this) // 数据对象, Dep, vmCount

    this.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) : val
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (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.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep) // Watcher拿到Dep
      if (!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.get

    if (!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)
      : noop
    sharedPropertyDefinition.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`通知.

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:24:08  更:2022-10-17 12:24:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 15:05:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码