学习vue源码—mvvm

栏目: 后端 · 发布时间: 5年前

内容简介:这一篇主要是讲解一下vue里mvvm的原理,以及如何理解vue实现mvvm。稍微有去了解过vue是如何双向绑定的我们都很容易知道vue是通过

这一篇主要是讲解一下vue里mvvm的原理,以及如何理解vue实现mvvm。

稍微有去了解过vue是如何双向绑定的我们都很容易知道vue是通过 Object.defineProperty 劫持 data 属性的 settergetter ,但是这仅仅只是实现的一部分,在这个实现里我们还要理解 dep (订阅中心)和 watcher (订阅者)的概念。

dep —订阅中心

dep 代码在 ./src/core/observer/dep.js 文件里,下面简单讲解一下:

  1. dep 的定义参考了观察者设计模式,每一个 dep 有自己的唯一标识 id 和订阅者列表 subs
  2. addSubremoveSub 用来管理订阅者列表 subs
  3. depend 用来收集 watcher (订阅者)。
  4. notify 用来通知 watcher (订阅者)执行更新。
  5. Dep.target 刚开始看是比较难理解的一个概念, Dep.target 其实是调用当前 dep 对应属性的 watcher 。举个例子:假如 data 有个属性 name ,那么当 data.namegetter 被触发时,我们需要知道是谁在调用这个 data.namegetter ,这就是 Dep.target
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)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

复制代码

watcher —订阅者

watcher 代码在 ./src/core/observer/watcher.js 文件里,关于 watcher 的选项配置就不细说了,在这里我们只需要重点关注其中的 getupdaterunevaluate 这几个方法。

这几个方法的作用稍后解释,现在我们要先理解怎样才会产生一个 watcher 。在vue里面,有三种类型的 watcher

  1. 每一个组件的实例都是一个 watcher
  2. 在组件的 watch 选项中声明的 watcher
  3. 计算属性所使用的依赖值会给对应的依赖值添加一个 watcher

讲完 watcher 的来源后我们再来看这几个方法的讲解:

  1. 先从 update 讲起,当某个响应属性发生变化时触发 setter 后,执行 dep.notify 通知每个 watcher 执行 update ,代码比较简单,三个逻辑分支,判断 this.lazy ,这是应用于计算属性时会触发的逻辑分支, this.sync 则用于判断同步执行 watcher 的回调,否则推入 queueWatcher 后续执行。
  2. runevaluate 都是会调用 get 方法,只是 run 方法是用于组件实例的 watcherwatch 选项中声明的 watcherwatch 选项中声明的 watcherthis.usertrue ,在 run 方法中的 this.cb.call(this.vm, value, oldValue) 这段代码则是我们 watch 选项中触发的回调。至于 evaluate 方法则更加简单了,调用 get 方法然后设置 this.dirtyfalse 则是为了后续其他地方使用这个计算属性的时候不需要重新计算,这也是计算属性缓存的一部分逻辑。
  3. 接下来讲讲 get 方法, pushTarget(this) 这段则是设置 Dep.target 为当前 watcher 实例,其实就是告诉 dep 是谁在获取属性。 value = this.getter.call(vm, vm) 则是获取当前值,在这里三种类型的 watchergetter 是不一样的。
  4. 最后提一下,计算属性的值一般是在组件实例的 watcher 执行 getter 的过程中执行计算的。
watcher类型 getter
组件实例 render函数
watch 执行parsePath方法生成的函数
计算属性 执行createComputedGetter方法生成的函数
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } 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()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        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()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

复制代码

总结

其实学习vue的mvvm,重点在于 depwatcher 的理解,要明白这两个类的实例在双向绑定的过程中扮演的是一个什么样角色,单纯从代码上可能不太容易理解这样设计的意图,但是如果能有一个比较具象化的东西来对应,相信对你的理解会有非常大的帮助。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

产品型社群

产品型社群

李善友 / 机械工业出版社 / 2015-3-1 / CNY 69.00

传统模式企业正在直面一场空前的“降维战争”, 结局惨烈,或生或死。 传统模式很难避免悲惨下场, 诺基亚等昔日庞然大物轰然倒塌, 柯达发明了数码成像技术却依然破产, 新商业的兴起到底遵循的是什么模式? 微信轻而易举干掉了运营商的短信业务, “好未来”为何让传统教育不明觉厉? 花间堂为什么不是酒店,而是入口? 将来不会有互联网企业与传统企业之分, ......一起来看看 《产品型社群》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具