Part3-2-1 Vue.js 源码剖析-响应式原理

Vue主要使用了两种设计模式


观察者模式:由具体的目标调度,比如当事件触发,Dep发布者就会去调用Watcher观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的
发布订阅模式:由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在

观察者模式

  • 观察者(订阅者)—— Watcher
    • update():当事件发生时,具体要做的事情
  • 目标(发布者)—— Dep
    • subs数组:存储所有的观察者
    • addSub():添加观察者
    • notify():当事件发生,调用所有观察者的update()方法

响应式处理的入口

整个响应式处理的过程是比较复杂的,下面我们先从

src\core\instance\init.js

initState(vm) vm 状态的初始化 初始化了 _data、_props、methods 等

src\core\instance\state.js

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)

  // 数据的初始化
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  } 


  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initData(vm) vm 数据的初始化

function initData (vm: Component) {
  let data = vm.$options.data
  // 初始化 _data,组件中 data 是函数,调用函数返回结果
  // 否则直接返回 data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  // 获取 data 中的所有属性
  const keys = Object.keys(data)
  // 获取 props / methods
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 判断 data 上的成员是否和  props/methods 重名
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式处理
  observe(data, true /* asRootData */)
}

src\core\observer\index.js

observe(value, asRootData)
负责为每一个 Object 类型的 value 创建一个 observer 实例

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断 value 是否是对象
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果 value 有 __ob__(observer对象) 属性 结束
  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
  ) {
    // 创建一个 Observer 对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Observer

src\core\observer\index.js

对对象做响应化处理
对数组做响应化处理
export class Observer {
  // 观测对象
  value: any;
  // 依赖对象
  dep: Dep;
  // 实例计数器
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    // 初始化实例的 vmCount 为0
    this.vmCount = 0
    // 将实例挂载到观察对象的 __ob__ 属性
    def(value, '__ob__', this)
    // 数组的响应式处理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象创建一个 observer 实例
      this.observeArray(value)
    } else {
      // 遍历对象中的每一个属性,转换成 setter/getter
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    // 获取观察对象的每一个属性
    const keys = Object.keys(obj)
    // 遍历每一个属性,设置为响应式数据
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

walk(obj)

遍历 obj 的所有属性,为每一个属性调用 defineReactive() 方法,设置 getter/setter

defineReactive()

src\core\observer\index.js

defineReactive(obj, key, val, customSetter, shallow)

为一个对象定义一个响应式的属性,每一个属性对应一个 dep 对象

如果该属性的值是对象,继续调用 observe

如果给属性赋新值,继续调用 observe
如果数据更新发送通知

对象响应式处理

// 为一个对象定义一个响应式的属性
/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建依赖对象实例
  const dep = new Dep()
  // 获取 obj 的属性描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // 提供预定义的存取器函数
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 判断是否递归观察子对象,并将子对象属性都转换成 getter/setter,返回子观察对象
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果存在当前依赖目标,即 watcher 对象,则建立依赖
      if (Dep.target) {
        dep.depend()
        // 如果子观察目标存在,建立子对象的依赖关系
        if (childOb) {
          childOb.dep.depend()
          // 如果属性是数组,则特殊处理收集数组对象依赖
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      // 返回属性值
      return value
    },
    set: function reactiveSetter (newVal) {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果新值等于旧值或者新值旧值为NaN则不执行
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // 如果没有 setter 直接返回
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      // 如果预定义setter存在则调用,否则直接更新新值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值是对象,观察子对象并返回 子的 observer 对象
      childOb = !shallow && observe(newVal)
      // 派发更新(发布更改通知)
      dep.notify()
    }
  })
}

数组的响应式处理

Observer 的构造函数中

// 数组的响应式处理
if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
}
// 为数组中的每一个对象创建一个 observer 实例
  this.observeArray(value)
} else {
// 编译对象中的每一个属性,转换成 setter/getter
  this.walk(value)
}
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

处理数组修改数据的方法

src\core\observer\array.js

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
// 使用数组的原型创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
// 修改数组元素的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  // 保存数组原方法
  const original = arrayProto[method]
  // 调用 Object.defineProperty() 重新定义修改数组的方法
  def(arrayMethods, method, function mutator (...args) {
    // 执行数组的原始方法
    const result = original.apply(this, args)
    // 获取数组对象的 ob 对象
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 对插入的新元素,重新遍历数组元素设置为响应式数据
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 调用了修改数组的方法,调用数组的ob对象发送通知
    ob.dep.notify()
    return result
  })
})

Dep

src\core\observer\dep.js
依赖对象
记录 watcher 对象
depend() -- watcher 记录对应的 dep
发布通知

1. defineReactive() getter 中创建 dep 对象,并判断 Dep.target 是否有值(一会
再来看有什么时候有值得) , 调用 dep.depend()
2. dep.depend() 内部调用 Dep.target.addDep(this) ,也就是 watcher addDep()
法,它内部最调用 dep.addSub(this) ,把 watcher 对象,添加到 dep.subs.push(watcher) 中,也
就是把订阅者添加到 dep subs 数组中,当数据变化的时候调用 watcher 对象的 update() 方法
3. 什么时候设置的 Dep.target? 通过简单的案例调试观察。调用 mountComponent() 方法的时
候,创建了渲染 watcher 对象,执行 watcher 中的 get() 方法
4. get() 方法内部调用 pushTarget(this) ,把当前 Dep.target = watcher ,同时把当前
watcher 入栈,因为有父子组件嵌套的时候先把父组件对应的 watcher 入栈,再去处理子组件的 watcher,子组件的处理完毕后,再把父组件对应的 watcher 出栈,继续操作
5. Dep.target 用来存放目前正在使用的 watcher 。全局唯一,并且一次也只能有一个 watcher
被使用

/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0
// dep 是个可观察对象,可以有多个指令订阅它
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  // 静态属性,watcher 对象
  static target: ?Watcher;
  // dep 实例 Id
  id: number;
  // dep 实例对应的 watcher 对象/订阅者数组
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  // 添加新的订阅者 watcher 对象
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  // 移除订阅者
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  // 将观察对象和 watcher 建立依赖
  depend () {
    if (Dep.target) {
      // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
      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)
    }
    // 调用每个订阅者的update方法实现更新
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
// Dep.target 用来存放目前正在使用的watcher
// 全局唯一,并且一次也只能有一个watcher被使用
// 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 = []
// 入栈并将当前 watcher 赋值给 Dep.target
// 父子组件嵌套的时候先把父组件对应的 watcher 入栈,
// 再去处理子组件的 watcher,子组件的处理完毕后,再把父组件对应的 watcher 出栈,继续操作
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  // 出栈操作
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

Watcher

Watcher 分为三种, Computed Watcher 、用户 Watcher ( 侦听器 ) 渲染 Watcher
渲染 Watcher 的创建时机
/src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
渲染 wacher 创建的位置 lifecycle.js mountComponent 函数中
Wacher 的构造函数初始化,处理 expOrFn (渲染 watcher 和侦听器处理不同)
调用 this.get() ,它里面调用 pushTarget() 然后 this.getter.call(vm, vm) (对于渲染 wacher
updateComponent ),如果是用户 wacher 会获取属性的值(触发 get 操作)
当数据更新的时候, dep 中调用 notify() 方法, notify() 中调用 wacher update() 方法
update() 中调用 queueWatcher()
queueWatcher() 是一个核心方法,去除重复操作,调用 flflushSchedulerQueue() 刷新队列并执行
watcher
flflushSchedulerQueue() 中对 wacher 排序,遍历所有 wacher ,如果有 before ,触发生命周期
的钩子函数 beforeUpdate ,执行 wacher.run() ,它内部调用 this.get() ,然后调用 this.cb() ( 渲染
wacher cb noop)
整个流程结束

响应式原理总结

a

methods: {
  handler () {
    // Vue 无法探测普通的新增属性
    this.obj.count = 555

    // vue 由于考虑性能问题,没有对数组中的每一个属性进行过处理,因此以下2项不是响应式的,数组数据改变了,但是页面视图不会更新
    this.arr[0] = 1
    this.arr.length = 0

    // vue 对数组中的 push,pop,shift,unshift,splice,sort,reverse 这7个改变自身的方法进行了重写,因此使用数组的这些方式时,数据是响应式的
    this.arr.push(4)
} }

转换成响应式数据

methods: {
  handler () {
    this.$set(this.obj, 'count', 555)
    this.$set(this.arr, 0, 1)
    this.arr.splice(0)
} }

实例方法/数据

vm.$set

功能

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于 向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如

this.myObject.newProperty = 'hi')

注意:对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

vm.$set(obj, 'foo', 'test')

定义位置

Vue.set()

global-api/index.js

// 静态方法 set/delete/nextTick 
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

vm.$set()

instance/index.js

// 注册 vm 的 $data/$props/$set/$delete/$watch 
// instance/state.js
stateMixin(Vue)

// instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del

源码

set() 方法

observer/index.js

回顾 defineReactive 中的 childOb,给每一个响应式对象设置一个 ob

调用 $set 的时候,会获取 ob 对象,并通过 ob.dep.notify() 发送通知

export function set (target: Array<any> | Object, key: any, val: any): any
{
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
){
warn(`Cannot set reactive property on undefined, null, or primitive
value: ${(target: any)}`)
  }
// 判断 target 是否是对象,key 是否是合法的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key) // 通过 splice 对key位置的元素进行替换
// splice 在 array.js进行了响应化的处理 target.splice(key, 1, val)
return val }
// 如果 key 在对象中已经存在直接赋值
if (key in target && !(key in Object.prototype)) {
    target[key] = val
return val }
// 获取 target 中的 observer 对象
const ob = (target: any).__ob__
// 如果 target 是 vue 实例或者$data 直接返回 if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data
'+
'at runtime - declare it upfront in the data option.'
)
return val }
// 如果 ob 不存在,target 不是响应式对象直接赋值 if (!ob) {
    target[key] = val
return val }
// 把 key 设置为响应式属性 defineReactive(ob.value, key, val) // 发送通知
ob.dep.notify()
return val
}

vm.$delete

功能

删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。

注意:目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。

vm.$delete(vm.obj, 'msg')

定义位置

Vue.delete()

global-api/index.js

// 静态方法 set/delete/nextTick 
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

vm.$delete()

instance/index.js

// 注册 vm 的 $data/$props/$set/$delete/$watch 
stateMixin(Vue)

// instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del

源码

src\core\observer\index.js

/**
 * Delete a property and trigger change if necessary.
 */
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target)) ){
    warn(`Cannot delete reactive property on undefined, null, or primitive
value: ${(target: any)}`)
}
// 判断是否是数组,以及 key 是否合法
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 如果是数组通过 splice 删除 // splice 做过响应式处理 target.splice(key, 1) return
}
// 获取 target 的 ob 对象
const ob = (target: any).__ob__
// target 如果是 Vue 实例或者 $data 对象,直接返回 if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
)
return
}
// 如果 target 对象没有 key 属性直接返回 if (!hasOwn(target, key)) {
return
}
// 删除属性
delete target[key] if (!ob) {
return
}
// 通过 ob 发送通知
  ob.dep.notify()
}

vm.$watch

vm.$watch( expOrFn, callback, [options] )  

功能
观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只
接受监督的键路径。对于更复杂的表达式,用一个函数取代。

参数

expOrFn :要监视的 $data 中的属性,可以是表达式或函数
callback :数据变化后执行的函数
  • 函数:回调函数
  • 对象:具有 handler 属性(字符串或者函数),如果该属性为字符串则 methods 中相应的定义
options :可选的选项
  • deep:布尔类型,深度监听
  • immediate:布尔类型,是否立即执行一次回调函数

示例

const vm = new Vue({
  el: '#app',
  data: {
    a: '1',
    b: '2',
    msg: 'Hello Vue',
    user: {
      firstName: '诸葛',
      lastName: '亮'
    }
  }
})

// expOrFn 是表达式 
vm.$watch('msg',
  function (newVal, oldVal) {
    console.log(newVal, oldVal)
  })

vm.$watch('user.firstName',
  function (newVal, oldVal) {
    console.log(newVal)
  })

// expOrFn 是函数 
vm.$watch(function () {
  return this.a + this.b
}, function (newVal, oldVal) {
  console.log(newVal)
})

// deep 是 true,消耗性能 
vm.$watch('user',
  function (newVal, oldVal) {
    // 此时的 newVal 是 user 对象 
    console.log(newVal === vm.user)
  },
  { deep: true }
)

// immediate 是 true 
vm.$watch('msg',
  function (newVal, oldVal) {
    console.log(newVal)
  },
  { immediate: true }
)

三种类型的 Watcher 对象

没有静态方法,因为 $watch 方法中要使用 Vue 的实例
Watcher 分三种:计算属性 Watcher 、用户 Watcher ( 侦听器 ) 、渲染 Watcher
创建顺序:计算属性 Watcher 、用户 Watcher ( 侦听器 ) 、渲染 Watcher
vm.$watch()
        src\core\instance\state.js

源码
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    // 获取 Vue 实例 this
    const vm: Component = this
    if (isPlainObject(cb)) {
      // 判断如果 cb 是对象执行 createWatcher
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 标记为用户 watcher
    options.user = true
    // 创建用户 watcher 对象
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 判断 immediate 如果为 true
    if (options.immediate) {
      // 立即执行一次 cb 回调,并且把当前值传入
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    // 返回取消监听的方法
    return function unwatchFn () {
      watcher.teardown()
    }
  }
查看渲染 watcher 的执行过程
当数据更新, defifineReactive set 方法中调用 dep.notify()
调用 watcher update()
调用 queueWatcher() ,把 wacher 存入队列,如果已经存入,不重复添加
循环调用 flflushSchedulerQueue()
  • 通过 nextTick(),在消息循环结束之前时候调用 flflushSchedulerQueue()
调用 wacher.run()
  • 调用 wacher.get() 获取最新值
  • 如果是渲染 wacher 结束
  • 如果是用户 watcher,调用 this.cb()

异步更新队列-nextTick()

Vue 更新 DOM 是异步执行的,批量的

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

vm.$nextTick(function () { /* 操作 DOM */ }) / Vue.nextTick(function () {})

vm.$nextTick() 代码演示

<div id="app"> 
  <p ref="p1">
    {{ msg }}
  </p> 
</div> 

<script src="../../dist/vue.js"></script> 
<script> 
  const vm = new Vue({ 
    el: '#app', 
    data: { 
      msg: 'Hello nextTick', 
      name: 'Vue.js', 
      title: 'Title' 
    },
    mounted() { 
      this.msg = 'Hello World' 
      this.name = 'Hello snabbdom' 
      this.title = 'Vue.js' 
      this.$nextTick(() => { 
        console.log(this.$refs.p1.textContent) 
      }) 
    } 
  }) 
</script>

定义位置

src\core\instance\render.js
Vue.prototype.$nextTick = function (fn: Function) { 
    return nextTick(fn, this) 
}

源码

  • 手动调用 vm.$nextTick()
  • Watcher queueWatcher 中执行 nextTick()
  • src\core\util\next-tick.js
/* @flow */
/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc

// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把 cb 加上异常处理存入 callbacks 数组中
  callbacks.push(() => {
    if (cb) {
      try {
        // 调用 cb()
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 调用
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    // 返回 promise 对象
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

nextTick按照特定异步策略执⾏队列操作

data() {
    return {
      msg: "ready",
    };
  },
  mounted() {
    Promise.resolve().then(() => {
      console.log("promise");
    });
    this.msg = "ready1";
    this.msg = "ready2";
    this.msg = "ready3";

    this.$nextTick(() => {
      console.log("nextTick", this.msg); // nextTick 依然在 Promise 前执行
    });
  },

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值