Vue2——源码阅读

168 阅读8分钟

实例化过程以及响应式原理

全局 API 初始化

// src/core/index.js
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
// ...

initGlobalAPI(Vue)

// ...

export default Vue

// src/core/global-api
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { set, del } from '../observer/index'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // ...
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  extend(Vue.options.components, builtInComponents)

  initUse(Vue) // 初始化 Vue.use
  initMixin(Vue) // 初始化 Vue.mixin
  initExtend(Vue) // 初始化 Vue.extend
  // ...
}

实例 API 混入

// src/core/intance/index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'

function Vue (options) {
  // ...
  this._init(options)
}

// 为 Vue 实例混入了 _init
initMixin(Vue)

// 为 Vue 实例混入了 $data/$props/$set/$delete/$watch
stateMixin(Vue)

// 为 Vue 实例混入了 $on/$off/$once/$emit
eventsMixin(Vue)

// 为 Vue 实例混入了 _update/$forceUpdate/$destroy
lifecycleMixin(Vue)

// 为 Vue 实例混入了 $nextTick/_render
renderMixin(Vue)

export default Vue

initMixin

// src/core/instance/init.js

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
      // ...
  }
}

详见 _init

stateMixin

// src/core/instance/state.js

import {
  set,
  del,
  observe,
} from '../observer/index'

export function stateMixin (Vue: Class<Component>) {
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
    
  // ...
    
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set // 涉及到了 observer, 后面再看
  Vue.prototype.$delete = del // 涉及到了 observer, 后面再看

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    // 涉及到了 observer 和 watcher, 后面再看
  }
}

eventsMixin

// src/core/instance/events.js
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    
    // 如果传入的 event 是一个包含事件名的数组
    // 则用一个 for 循环取出每个事件名并递归调用 $on 来注册事件
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    }
    // 如果传入的是一个字符串
    // 当实例的 _events 中存在该事件的监听器数组则 push
    // 否则为该实例初始化一个数组并 push
    else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
  
  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all —— 没有传入任何参数时, 通过重置 _events 来取消实例的所有事件监听
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
      
    // array of events —— 批量取消事件监听, 传入数组
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn) // 递归调用
      }
      return vm
    }
      
    // specific event —— 需要取消一个指定事件的所有处理器
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
      
    // specific handler —— 取消一个指定事件的指定处理器
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }
  
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    // 调用时将会调用 $off 取消自己
    // 并调用传入的回调函数
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    
    // ...
    
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

总结

$on 原理 —— 向 _events 对象中的对应处理器数组 push 一个处理函数

$off 原理 ——

  1. 取消所有时将 _events 重置
  2. 批量取消时, 循环一遍传入的指定取消事件数组, 递归调用 $off
  3. 指定事件取消时, 清空 _events 对象上指定的数组
  4. 指定事件和处理器时, 循环一遍对应的处理器数组并移除即可 (暴力 while)

$once 原理 —— 定义一个 on 函数, 该函数调用时会调用 $off 来取消自己, 并调用传入的回调函数. 然后用 $on 方法监听该事件, 并传入这个 on 函数为回调

$emit 原理 —— 一个 for-each 调用 _events 对象中指定的事件处理器数组

lifecycleMixin

// src/core/instance/lifecycle.js
export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    // ...
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    // ...
  }
}

renderMixin

// src/core/instance/render.js
export function renderMixin (Vue: Class<Component>) {
  // ...
    
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
      // ...
  }
}

实例 API 初始化

_init

实例构造时立刻调用了 _init 函数

// src/core/instance/init.js

 // import ...
let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    
    vm._uid = uid++

    // 防止 vm 实例被 observe 的标记
    vm._isVue = true
      
    // 合并选项...(代码略)
      
    // expose real self
    vm._self = vm
    
    // 为实例初始化了 $root/$parent/$children/$ref/_watcher/_isMounted
    initLifecycle(vm)
      
    // 为实例初始化父组件附加的事件
    initEvents(vm)
      
    // 初始化了实例的 _vnode/$slot/$scopeSlots 等属性, 并调用 defineReactive 定义了 $attr/$listeners
    initRender(vm)
      
    callHook(vm, 'beforeCreate')
      
    // resolve injections before data/props
    initInjections(vm)
      
    initState(vm)
    
    // resolve provide after data/props
    initProvide(vm)
      
    callHook(vm, 'created')

    // ...

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
// ...

_init过程总结

  • 分配 uid, 标记自己为 Vue 实例
  • 合并选项
  • 初始化生命周期
  • 初始化事件
  • 初始化渲染相关的属性, 函数, 调用 defineReactive 定义 实例的 $listeners$attrs 选项
  • 执行 beforeCreated 钩子
  • inject 初始化
  • 状态初始化
  • provide 初始化
  • 执行 created 钩子
  • 调用 $mount 挂载实例
// src/core/instance/lifecycle.js

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
    
  pushTarget() // src/core/observer/dep.js 文件中定义
    
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
    
  popTarget() // src/core/observer/dep.js 文件中定义
}

生命周期钩子执行原理

  • 通过 callHook 函数, 函数传入一个 vm 实例和一个字符串 hook 用于指定生命周期钩子
  • 通过 vm.$options[hook] 获取该钩子的所有处理器 handlers, 并依次调用
  • 如果实例的 _hasHookEvent 属性为 true$emit 该钩子, 并为钩子名加上 "hook:" 前缀

initLifecycle

// src/core/instance/lifecycle

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initEvents

// src/core/instance/events.js
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

initRender

// src/core/instance/render.js
export function initRender (vm: Component) {
    // 初始化了实例的 _vnode/$slot/$scopeSlots 等属性
    // 通过 defineReactive 函数定义了 $attr 和 $listeners
}

initInjections

初始化依赖注入

// src/core/instance/inject.js
export function initInjections (vm: Component) {
  // 解析 vm.$options.inject 属性
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    // ...
    Object.keys(result).forEach(key => {
      // 调用 defineReactive 将解析结果 result 的每个属性定义成响应式
    })
	// ...
  }
}

initState

初始化状态 (状态 = prop + method + data + computed + watcher)

// 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

初始化 data

// src/core/instance/state.js

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
    
  // 警告 data 函数要返回纯对象
  if (!isPlainObject(data)) {
    data = {}
    // 警告 : ...
  }
    
  // 在实例上代理 data
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // 检查 key 是否有冲突, 若没有则调用 proxy 函数进行代理
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

总结

  • initData 函数初始化 data, 对一些 data 属性进行代理, 并调用 observe 函数为 data 创建观察者
observe

observe 函数为一个任意值 value 创建一个 Observer (一个 value 一个 Observer)

// src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 避免重复定义 observer
    ob = value.__ob__
  } else if (
    // 判断 "value" 是否应该观察 并且
    // 是否不在服务端渲染 并且
    // 是否是数组或一个纯对象 并且
    // 是否是可扩展的 并且
    // 并且是否不是一个 Vue 实例
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) ob = new Observer(value) // 为 value 创建一个 Observer
    
  if (asRootData && ob) ob.vmCount++
  return ob // 返回该 Observer
}

总结

  • observe 函数通过调用 Observer 的构造函数为一个观察对象创建观察者, 并返回该观察者
Observer
// src/core/observer/index.js
import Dep from './dep'
import { arrayMethods } from './array' // 导出 array 文件中进行过包装的数组异变方法
import {
  def,
  hasProto,
  /** ... */
} from '../util/index'

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * Observer(观察者) 类被附加到每个被观察的对象上.
 * 一旦附加上, 这个观察者转换目标对象的属性为 getter/setter
 * 并利用它们进行依赖收集和发送更新
 */
export class Observer {
  value: any;
  dep: Dep; // dependency 的缩写, 用于收集对该数据的依赖者
  
  // 以该对象(指的是 value ?) 作为根 $data 的实例数量
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this) // 为观察对象定义 __ob__ 属性, 将属性值设置为当前观察者
      
    if (Array.isArray(value)) {
      // 如果支持 __proto__ 属性, 则将经过特殊处理的 arrayMethods 作为 value 的原型
      if (hasProto)
        protoAugment(value, arrayMethods)
      // 否则使用 Object.defineProperty 来将这些数组方法定义到 value 上
      else
        copyAugment(value, arrayMethods, arrayKeys)
        // const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
        
      this.observeArray(value)
    }
    else this.walk(value)
  }
    
  // 走一遍 obj 的所有属性并转化它们为 getter/setter
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++)
      defineReactive(obj, keys[i])
  }

  // 遍历并观察所有数组项
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++)
      observe(items[i])
  }
}

// 将目标的原型设置成指定源
function protoAugment (target, src: Object) {
  target.__proto__ = src
}

// 通过定义隐藏属性来增强目标对象或数组
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])
  }
}

总结

  • 观察者构造器为观察对象定义 __ob__ 属性, 将自己作为观察对象的观察者
  • 每个观察者有一个(has-a)依赖对象 dep: Dep
  • 观察者会根据观察对象的类型进行不同策略.
    • 如果是数组, 则调用 observeArray 来观察 observe 每个数组项. 同时它还会将为数组对象定义经过包装的异变数组方法.
    • 如果是对象, 它会调用 walk 方法, 走一遍被观察对象的所有属性并调用 defineReactive 函数将所有属性转化为 getter/setter

了解了 Observer 后, 回过头来研究一下在 stateMixin 阶段被混入的 $set$delete 实例 API

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
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)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  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
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

/**
 * 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)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  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
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

总结

  • set 获取定义目标的观察者 (Observer), 调用 defineReactive 为目标定义响应式数据, 并让目标的观察者通知依赖更新
  • delete 删除目标的响应式数据, 并让目标的观察者通知依赖更新
defineReactive
// src/core/observer/index.js

// 在 "obj" 对象上定义一个响应式的属性
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 这个 dep 将与我们要定义的响应式属性的 getter 和 setter 形成闭包
  // 以此为定义响应式的响应式属性进行依赖收集
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) return

  // 保存原始的 getter 和 setter
  const getter = property && property.get
  const setter = property && property.set
  
  // 如果 (obj 没有getter 或者有 setter) 并且 (defineReactive 只传入了前两个参数)
  if ((!getter || setter) && arguments.length === 2)
    val = obj[key]

  // 获得 val 的 Observer
  let childOb = !shallow && observe(val)
  
  // 定义 obj 的 getter 和 setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // get 时进行依赖收集
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
            
          /** src/core/observer/index.js
			function dependArray (value: Array<any>) {
  				for (let e, i = 0, l = value.length; i < l; i++) {
    			e = value[i]
			    e && e.__ob__ && e.__ob__.dep.depend()
			    if (Array.isArray(e)) dependArray(e)
			  }
			} */
          if (Array.isArray(value)) dependArray(value)
        }
      }
      return value
    },
    // set 时通知依赖进行更新
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      
      if (newVal === value || (newVal !== newVal && value !== value)) return
      
      if (process.env.NODE_ENV !== 'production' && customSetter) customSetter()
      
      if (getter && !setter) return
        
      if (setter) setter.call(obj, newVal)
      else val = newVal
        
      childOb = !shallow && observe(newVal)
        
      dep.notify()
    }
  })
}

总结

  • defineReactive 函数为一个对象定义响应式属性
  • defineReactive 函数为响应式属性定义 gettersetter, 并用一个 dep 与它们形成闭包, 从而对响应式属性进行依赖收集
  • 响应式属性在 get 时将收集它的依赖, 以及该属性对应的值的 __ob__ 的依赖
  • 响应式属性在 set 时将通知它的依赖进行更新
Dep
// src/core/observer/dep.js
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0


// dep 是一个可观察的, 拥有许多订阅的指令 (即一个发布者)
export default class Dep {
  static target: ?Watcher; // 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 () {
    // 为依赖目标 Watcher 添加当前依赖 (订阅者 Dep.target 订阅了发布者 this)
    if (Dep.target)
      Dep.target.addDep(this)
  }

  // 通知该依赖的订阅者进行 update
  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()
  }
}

总结

  • Dep 类作为一个发布者, 拥有许多的订阅者 (Watcher)
  • Dep 类的静态属性 target 用于全局存储当前订阅者
  • depend 函数用于收集依赖, 将该依赖 this 添加到 target 这个订阅者中
  • notify 函数用于通知所有在 subs 数组里的订阅者进行 update
Watcher
// src/core/observer/watcher.js

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * 一个 wacher 编译一个表达式, 收集它依赖的依赖,
 * 并在表达式值被改变时触发一个 callback
 * 它被同时用于 $watch 和指令中
 */
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()
  }

  // 计算 getter, 重新收集依赖
  get () {
      
    /** src/core/observer/dep.js
    export function pushTarget (target: ?Watcher) {
    	targetStack.push(target)
    	Dep.target = target
	} */
    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)
      
      /** src/core/observer/dep.js
      export function popTarget () {
      	  targetStack.pop()
      	  Dep.target = targetStack[targetStack.length - 1]
	  } */
      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)
    }
  }

  // 清理依赖收集
  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
  }

  // 订阅者接口, 会在依赖改变时被调用
  update () {
    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)
      }
    }
  }

  // 计算 watcher 的值, 这个只会被懒 watcher 调用
  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()
  }

  // 将自己从所有发布者的订阅者列表中移除
  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
    }
  }
}

Watcher 总结

  • 一个 wacher 编译一个表达式, 收集它依赖的依赖, 并在表达式值被改变时触发一个 callback
  • 它在 get 时将会将自己推送到全局的 Dep.target 中, 让依赖收集它作为订阅者

回顾 $watch :

export function stateMixin (Vue: Class<Component>) {
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

$watch原理

为指定的表达式或函数 expOrFn 创建一个 Watcher

总结

回顾一下,Vue响应式原理的核心就是ObserverDepWatcher

Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher

在数据被改的时候,触发set方法,通过对应的所有依赖 (Watcher),去执行更新。比如watchcomputed就执行开发者自定义的回调方法

initProvide

export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

数组异变方法拦截原理

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

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

// 拦截异变方法并发出事件
methodsToPatch.forEach(function (method: string) {
  // 缓存原函数
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    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.dep.notify()
    return result
  })
})
// src/core/util/lang.js

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

总结

拦截每个异变方法, 通过一个 forEach 循环, 让每个异变方法通过 Object.defineProperty 函数变成一个接收任意数量参数的 mutator

在这个 mutator 中, 首先调用了原始的函数, 然后进行了一个 switch 语句判断当前操作为哪个异变方法.

如果进行了 push unshift, 则这个 mutator 会缓存参数, 并让实例的观察者 __ob__ 去 observe 这个缓存的参数数组.

最后再让实例的 __ob__ 通知依赖更新.

VNode 算法

sameVnode 判断

  • key 相同
  • tag 相同
  • 同时为 comment 或 不为 commment
  • 两 node 的 data 真值相同
  • 为 input tag 时 type 相同

patch -> patchVnode -> updateChildren

// 主要功能 : 
// 对比两个 Vnode, 将差异更新到视图
function patch(oldVnode, vnode, parentElm) {
    // 没有旧节点
    if (!oldVnode) {
        // 直接给 paretElm 加上 vnode
    }
    // 有旧节点但没有新节点
    else if (!newVnode) {
        // 删除父元素的 oldVnode
    }
    else {
        if (sameVnode(oldVnode, vnode)) patchVnode(oldVnode, vnode)
        else {
            // 删除 paretElm 的 oldVnode
            // 给 parentElm 加上 vnode
        }
    }
}
function patchVnode(oldVnode, vnode) {
    if (oldVnode === vnode) return
    
    if (vnode.isStatic && oldVnode.isStatic && vnode.key === oldVnode.key) {
        vnode.elm = oldVnode.elm
        vnode.componentInstance = oldVnode.componentInstance
        return
    }
    
    const elm = vnode.elm = oldVnode.elm
    const oldCh = oldVnode.children
    const ch = vnode.children
    
    // 文本节点
    if (vnode.text) {
        // 替换元素的 content
    }
    else {
        // 旧子vnode 和 新子vnode 同时存在且不同
        if (oldCh && ch && (oldCh !== ch)) updateChildren(elm, oldCh, ch)
        // 只有 新子vnode 存在
        else if (ch) {
            if (oldVnode.text) {
                // 如果旧Vnode 是文本节点, 清空文本
            }
            // 为 elm 添加 子vnode
        }
        // 旧子vnode存在但新子vnode不存在
        else if (oldCh) {
            // 移除旧子vnode
        }
        // 如果两个新旧子vnode都不存在
        // 且 旧vnode 为文本节点
        else if (oldVnode) {
            // 清空 elm 的 content
        }
    }
}
function updateChildren(parentElm, oldCh, newCh) {
    // oldStartIndex  oldEndIndex newStartIndex newEndIndex
    let osi = 0, oei = oldCh.length - 1, nsi = 0, nei = newCh.length - 1
    // oldStartVnode oldEndVnode newStartVnode newEndVnode
    let osv = oldCh[0], oev = oldCh(oei), nsv = newCh[0], nev = newCh[nei]
    
    let oldKeyToIdx, idxInOld, elToMove, refElm
    
    while (osi <= oei && nsi <= nei) {
        if (!osv) osv = oldCh[++osi]
        else if (!oev) oev = oldCh[--oei]
        // 2 * 2 比较
        else if (sameVnode(osv, nsv)) {
            patchVnode(osv, nsv)
            // 更新索引和节点指向
        }
        else if (sameVnode(oev, nev)) {
            patchVnode(oev, nev)
            // 更新所有和节点指向
        }
        else if (sameVnode(osv, nev)) {
            patchVnode(osv, nev)
            // 将 oldStartVnode 移动到 oldEndIdx 后面的位置
            // 更新索引和节点指向
        }
        else if (sameVnode(oev, nsv)) {
            patchVnode(oev, nsv)
            // 将 oldEndVnode 移动到 oldStartIdx 前面的位置
            // 更新索引和节点指向
        }
        // 上述情况都不符合
        else {
            // 为 oldCh 创建 vnode key 映射到 oldCh 数组中索引的 map
            // 以实现 key 到 index 的快速查找
            
            let elmToMove
        }
    }
    
    if (osi > oei) {
        // 添加区间 [nsi, nei] 的 vnode
    }
    else if (nsi > nei) {
        // 移除区间 [osi, oei] 的 vnode
    }
}

next-tick

// src/core/util/next-tick.js

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    }
    else if (_resolve) _resolve(ctx)
  })
    
  if (!pending) {
    pending = true
    // timerFunc 是一个根据环境不同而定义的计时函数, 优先级依次是 : 
    // Promise, MutationObserver, setImmediate, setTimeout
    timerFunc()
  }

  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

JS 的 event loop 执行时会区分 task 和 microtask,引擎在每个 task 执行完毕,从队列中取下一个 task 来执行之前,会先执行完所有 microtask 队列中的 microtask setTimeout 回调会被分配到一个新的 task 中执行,而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行,会比 setTimeout 产生的 task 先执行。 要创建一个新的 microtask,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。 实在不行,只能用 setTimeout 创建 task 了。 为啥要用 microtask? 根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。 反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

为什么异步更新视图?

假设这样的情况 :

<template>
  <div>
    <div ref="test">{{test}}</div>
    <button @click="handleClick">tet</button>
  </div>
</template>
export default {
    data () {
        return {
            test: 0
        };
    },
    mounted () {
      for(let i = 0; i < 1000; i++) {
        this.test++;
      }
    }
}

moutedtest 的值要自增一千次. 根据响应式原理, 这将会触发 setter -> Dep -> Watcher -> update -> patch

如果没有异步更新那么每次 ++ 都会更新视图. 非常消耗性能. 因此, Vue 实现了一个 queue 队列, 在下一个 tick 时同一执行 queue 中 watcher 的 run 方法. 同时, 同 id 的 watcher 不会再次进入 queue 中, 因此不会执行 1000 次 watcher 的 run

<keep-alive>

src/core/components/keep-alive.js

组件属性&prop

组件属性, prop

// src/core/components/keep-alive.js

const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
  name: 'keep-alive',
  abstract: true, // 抽象组件, 不渲染为真实元素

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  }
    
  //...
}

created 钩子

created 钩子 : 创建缓存对象 cache

// src/core/components/keep-alive.js

export default {
  // ... 

  created () {
    this.cache = Object.create(null) // 用于缓存 VNode
    this.keys = []
  }
}

destoryed 钩子

destoryed 钩子 : 调用 prune(删除) 缓存实体函数来删除所有缓存实体

// src/core/components/keep-alive.js

export default {
  // ...
    
  created () {
    this.cache = Object.create(null)
    this.keys = [] // 缓存的实例 key, 数组前面的为 old key, 后面的为 new key
  }
    
  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  }
}

pruneCacheEntry 删除保存的 key 并且销毁对应的 vnode 的组件实例

// src/core/components/keep-alive.js

type VNodeCache = { [key: string]: ?VNode };

function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key] // 缓存的 VNode
  
  // 销毁缓存 vnode 的组件实例
  if (cached && (!current || cached.tag !== current.tag))
    cached.componentInstance.$destroy()
      
  cache[key] = null
  remove(keys, key)
}
// src/shared/util.js

/**
 * Remove an item from an array.
 */
export function remove (arr: Array<any>, item: any): Array<any> | void {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

destoryed -> pruneCacheEntry -> cached.componentInstance.$destory() & remove(keys, key)


mounted 钩子

mounted 钩子 : 调用 $watch 实例方法来观察 includeexclude 这两个 prop, 发生变化时调用 prune(修改) 缓存函数

// src/core/components/keep-alive.js

const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
  // ...
    
  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },
    
  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
      
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  }
}

function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)
    return pattern.indexOf(name) > -1
  else if (typeof pattern === 'string')
    return pattern.split(',').indexOf(name) > -1
  else if (isRegExp(pattern))
    return pattern.test(name)
    
  return false
}
// src/shared/util.js

const _toString = Object.prototype.toString

export function isRegExp (v: any): boolean {
  return _toString.call(v) === '[object RegExp]'
}

pruneCache 函数第一个参数传入一个 keep-alive 实例 和一个过滤函数 filter

// src/core/components/keep-alive.js

function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
        
      if (name && !filter(name))
        pruneCacheEntry(cache, key, keys, _vnode)
    }
  }
}

// 根据VNode组件选项, 返回组件名
function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}
  1. mounted()

  2. (传入 pruneCache 作为回调) $watch('include', ...) & $watch('exclude', ...)

  3. ($watch 回调执行, 传入 this 作为 keepAliveInstance, match 作为 filter) pruneCache

  4. 调用 geComponentName 获取缓存组件的 name

  5. filter(name)

  6. pruneCacheEntry 删除不满足条件的缓存


render 函数

// src/core/components/keep-alive.js

export default {
  render () {
    // 获取 <keep-alive> 组件默认插槽中的 VNode
    const slot: VNode[] | undefined = this.$slots.default
    
    /* 得到默认 slot 插槽中的第一个组件对应的 vnode */
    const vnode: VNode = getFirstComponentChild(slot)
    
	// 简写 : 判断是否存在 vnode 并提取它的组件选项
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    
    // 如果获取的 vnode 存在组件选项, 说明该 vnode 对应一个组件 (而不是文本, comment?)
    if (componentOptions) {
      // check pattern
      
      // 获取该 vnode 对应组件名
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this
      
      // 不满足条件, 直接返回 vnode
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      
      // same constructor may get registered as different local components
      // so cid alone is not enough (#3269)
      const key: ?string = vnode.key == null ?
        componentOptions.Ctor.cid + 
          (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
        
        
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest (时刻将最新的 key 放在 keys 最后)
        remove(keys, key)
        keys.push(key)
      }
      else {
        cache[key] = vnode
        keys.push(key)
        // prune oldest entry 删除最旧的缓存实体
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true
    }
    // 如果存在 vnode 则返回, 否则如果存在插槽, 返回插槽的第一个 vnode
    return vnode || (slot && slot[0])
  }
}
// src/core/vdom/helpers/get-first-component-child.js

import { isDef } from 'shared/util'
import { isAsyncPlaceholder } from './is-async-placeholder'

export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      const c = children[i]
      if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
        return c
      }
    }
  }
}
// src/core/vdom/helpers/is-async-placeholder.js
export function isAsyncPlaceholder (node: VNode): boolean {
  return node.isComment && node.asyncFactory
}
// src/shared/util.js
export function isDef (v: any): boolean {
  return v !== undefined && v !== null
}

总结

一、

<keep-alive> 是一个抽象的组件, 它有一个 abstract 属性指定为 true 来说明, 并且它的 render 函数也不传入任何参数(通常我们会传入一个 h 作为构造 VNode 的函数), 而是提取子 VNode 来渲染, 这也印证了它是抽象组件.

二、

该组件在 created 的时候会创建一个 cache 数组用于缓存 VNode, 一个 keys 数组用于保存键.

三、

该组件在 destroyed 的时候, 会将所有保存在 cache 的 VNode 对应的组件实例通过 pruneCacheEntry 函数来 $destroy

该组件在 mounted 时, 会调用 $watch 观察 includeexclude 这两个 prop, 在发生变化时执行一个 pruneCache 的回调. 该回调接收一个 <keep-alive> 实例和一个 filter 作为过滤器, 并将实例中所有不满足 filter 要求的缓存 VNode 所对应的组件通过 pruneCacheEntry 函数 $destory

四、

组件的 render 函数不像以往的组件一样接收一个 h 作为参数, 而是直接返回实例的子 VNode 进行渲染.

该函数首先会通过 getFirstChildComponent 获得实例的默认插槽中的第一个组件类型 VNode(即存在 componentOptions 属性).

如果 vnode 不满足 include 或满足 exclude, 是则直接返回该 vnode

否则提取该 vnode 的 key

如果缓存中存在该 key 对应的 vnode 则将缓存好的 vnode 的组件实例属性赋值给当前 vnode (这样做就能达到将缓存的数据同步到当前 vnode). 并且将该 keykeys 中删除, 并再次 pushkeys 中以保证它是最新的.

如果缓存中不存在该 key 对应的 vnode 则通过 key 将该 vnode 存到 cache 中, 同时将 key push 到 keys 中.

除此之外, 当指定了最大缓存数 max 并且 keys 数组长度大于 max, 就会将第一个也就是最旧的 key 移出, 并调用 pruneCacheEntry 函数移出该 key 对应的缓存组件

然后, 定义该 vnode.data.keepAlivetrue 来指出该 vnode 的数据要 keep alive.

最后, 返回 vnode(如果有的话), 否则返回默认插槽的第一个 vnode(如果有的话)