Vue.js源码逐行代码注解src下core下instance

2023-10-08 17:55:51 浏览数 (2)

直播敲Vue吗哈哈哈哈,参加吗

events.js

代码语言:javascript复制
/* @flow */

import {
  tip,
  toArray,
  hyphenate,
  formatComponentName,
  invokeWithErrorHandling
} from '../util/index'
import { updateListeners } from '../vdom/helpers/index'

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)
  }
}

let target: any

function add (event, fn) {
  target.$on(event, fn)
}

function remove (event, fn) {
  target.$off(event, fn)
}

function createOnceHandler (event, fn) {
  const _target = target
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }
  }
}

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

/**
 * 从 $on 和 $emit 的实现上也能看出,事件的原理,监听者和触发者都是组件自身
 */
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  // <com @custom-click="handleCllick"/>
  // 将所有的事件和对应的回调放到vm._events对象上,格式
  // {event1: [cb1,cb2,...], ...}
  // this.$on('custom-click',function(){xxx})
  /**
   * 监听实例上的自定义事件,vm._event = { eventName: [fn1, ...], ... }
   * @param {*} event 单个的事件名称或者有多个事件名组成的数组
   * @param {*} fn 当 event 被触发时执行的回调函数
   */
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 事件为数组的情况
    // this.$on([event1, event2, ...], function(){xxx})
    if (Array.isArray(event)) {
      /**
       * event 是有多个事件名组成的数组,则遍历这些事件,依次递归调用 $on
       */
      for (let i = 0, l = event.length; i < l; i  ) {
        vm.$on(event[i], fn)
      }
    } else {
      // 比如如果存在vm._events['custom-click'] = []
      // 一个事件可以设置多个响应函数
      // this.$on('custom-click', cb1)
      // this.$on('custom-click', cb2)
      // vm._event['custom-click'] = [cb1, cb2, ...]
      /**
       * 将注册的事件和回调以键值对的形式存储到 vm._event 对象中
       * vm._event = { eventName: [fn1, ...] }
       */
      (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
      // <comp @hook:mounted="handleHookMounted"/>
      /**
       * hookEvent,提供从外部为组件实例注入声明周期方法的机会
       * 比如从组件外部为组件的 mounted 方法注入额外的逻辑
       * 该能力是结合 callhook 方法实现的
       */
      if (hookRE.test(event)) {
        // 置为true,标记当前组件实例存在 hook event
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  // 先通过$on添加事件,然后在事件回调按时中先调用$off移除事件监听,再执行用户传递过来的回调函数
  /**
   * 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除
   * vm.$on   vm.$off
   * @param {*} event
   * @param {*} fn
   * @returns
   */
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    // 将用户传递进来的回调做了一层包装
    /**
     * 调用 $on, 只是 $on 的回调函数被特殊处理了,触发时,执行回调函数,先移除事件监听,然后执行你设置的回调函数
     */
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    // 将包装函数作为事件的回调函数添加
    vm.$on(event, on)
    return vm
  }

  // 移除vm._events对象上指定事件(key)的指定回调函数
  // 1.没有提供参数,将vm._events={}
  // 2.提供了第一个事件参数,表示vm._events[event] = null
  // 3.提供了两个参数,表示移除指定事件的指定回调函数
  // 一句总结就是操作通过$on设置的vm._events对象
  /**
   * 移除自定义事件监听器,即从 vm._event 对象中找到对应的事件,移除所有事件 或者 移除指定事件的回调函数
   * @param {*} event
   * @param {*} fn
   * @returns
   */
  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    /**
     * vm.$off() 移除实例上的所有监听器 => vm._events = {}
     */
    if (!arguments.length) {
      // 移除所有的事件监听器 => vue._events = {}
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    /**
     * 移除一些事件 event = [event1, ...],遍历 event 数组,递归调用 vm.$off
     */
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i  ) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    /**
     * 除了 vm.$off() 之外,最终都会走到这里,移除指定事件
     */
    // 获取指定事件的回调函数
    const cbs = vm._events[event]
    if (!cbs) {
      /**
       * 表示没有注册过该事件
       */
      return vm
    }
    if (!fn) {
      /**
       * 没有提供 fn 回调函数,则移除该事件的所有回调函数, vm._event[event] = null
       */
      // vm._events[event] = [cb1, cb2, ...] => vm._events[event] = null
      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
  }

  // 触发当前实例上的事件,附加参数都会传给监听回调
  /**
   * 触发实例上的指定事件,vm._event[event] => cbs => loop cbs => cb(args)
   * @param {*} event 事件名
   * @returns
   */
  Vue.prototype.$emit = function (event: string): Component {
    // <comp @customClick="handleClick"/>
    // $on('customClick', function() {})
    // <comp @custom-click="handleClick"/>
    // $on('custom-click', function)
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      /**
       * 将事件名转换为小写
       */
      const lowerCaseEvent = event.toLowerCase()
      // 意思是说,HTML 属性不区分大小写,所以你不能使用 v-on 监听小驼峰形式的事件名(eventName),而应该使用连字符形式的事件名(event-name)
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component `  
          `${formatComponentName(vm)} but the handler is registered for "${event}". `  
          `Note that HTML attributes are case-insensitive and you cannot use `  
          `v-on to listen to camelCase events when using in-DOM templates. `  
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    // 从vm._events对象中获取指定事件的所有回调函数
    // 从 vm._event 对象上拿到当前事件的回调函数数组,并一次调用数组中的回调函数,并且传递提供的参数
    let cbs = vm._events[event]
    if (cbs) {
      // 数组转换,类数组转换为数组
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      // this.$emit('custom-click', arg1, arg2)
      // args = [arg1, arg2]
      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
  }
}

index.js

代码语言:javascript复制
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// Vue 构造函数
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用 Vue.prototype._init 方法,该方法是在 initMixin 中定义的
  this._init(options)
}

// 定义 Vue.prototype._init 方法
initMixin(Vue)

// 原型上的方法:$data, $props, $set, $delete, $watch
stateMixin(Vue)

// 定义事件相关的方法 $on $emit $once $off
eventsMixin(Vue)

// _update, $destroy, $forceUpdate
lifecycleMixin(Vue)

// renderHelper, $nextTick _render
/**
 * 执行 intstallRenderHelpes,在 Vue.prototype 对象上安装运行时便利程序
 * 定义:
 * Vue.prototype.$nextTick
 * Vue.prototype._render
 */
renderMixin(Vue)

export default Vue

init.js

代码语言:javascript复制
/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

// initMixin 把_init 方法挂载在 Vue 原型 供 Vue 实例调用
/**
 * 定义 Vue.prototype._init
 * @param {*} Vue Vue 构造函数
 */
export function initMixin (Vue: Class<Component>) {
  // 负责 Vue 的初始化过程
  Vue.prototype._init = function (options?: Object) {
    // Vue实例
    const vm: Component = this
    // a uid 每次Vue实例做递增的
    // 每个 vue 实例都有一个 _uid,并且是依次递增的
    vm._uid = uid  

    // 初始化性能的度量 开始位置
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // 处理组件配置项
    // merge options
    if (options && options._isComponent) {
      /**
       * 每个子组件初始化时走这里,这里只做了性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码执行效率
       * 至于每个子组件的选项合并发生在两个地方:
       * 1、Vue.component 方法注册的全局组件在注册时做了选项合并
       * 2、{ components: { xxx } } 方式注册的局部组件在执行编译器生成的 render 函数时做了选项合并,包括根组件中的 components 配置
       */
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      // 子组件:性能优化,减少原型链的动态查找,提高执行效率
      initInternalComponent(vm, options)
    } else {
      // 根组件走这里:选项合并,将全局配置选项合并到根组件的局部配置上
      // 组件选项合并,其实发生在三个地方:
      // 1. Vue.component(CompName, Comp), 做了选项合并,合并的Vue内置的全局组件和用户自己的注册的全局组件,最终都会放到全局的components选项中
      // 2. { components: {xxx} }, 局部注册,执行编译器生成的render函数时做了选项合并,会合并全局配置项到组件局部配置项上
      // 3. 这里的根组件的情况了
      /**
       * 初始化根组件时走这里,合并 Vue 的全局配置到根组件的局部配置,比如 Vue.component 注册的全局组件最后会合并到 根组件实例的 components 选项中
       */
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      /**
       * 设置代理,将 vm 实例 上的属性代理到 vm._renderProxy
       */
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm

    // 重点,整个初始化最重要的部分,也是核心

    // 组件关系属性的初始化,比如:$parent $root $children
    /**
     * 初始化组件实例关系属性,比如 $parent, $children, $root, $refs 等
     */
    initLifecycle(vm)
    // 初始化自定义事件
    // <comp @click="handleClick"></comp>
    // 组件上事件的监听其实是子组件自己在监听,也就是说谁触发谁监听
    // this.$emit('click'), this.$on('click',function handleClick(){})
    /**
     * 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
     * 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关
     */
    initEvents(vm)
    // 初始化插槽,获取this.$slots,定义this._c, 即createElement方法,平时使用的h函数
    /**
     * 解析组件的插槽信息,得到 vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数
     */
    initRender(vm)
    // 执行beforeCreate声明周期函数
    /**
     * 调用 beforeCreate 钩子函数
     */
    callHook(vm, 'beforeCreate')
    // 初始化inject选项,得到 result[key] = val 形式的配置对象,并做响应式处理
    /**
     * 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行浅层的响应式处理(只处理了对象的第一层数据),并代理每个 key 到 vm 实例
     */
    initInjections(vm) // resolve injections before data/props
    // 响应式原理的核型,处理props methods computed data watch等选项
    /**
     * 数据响应式的重点,处理 props,methods,data,computed,watch
     */
    initState(vm)
    // 处理provide选项
    // 总结 provide,inject 的实现原理
    /**
     * 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
     */
    initProvide(vm) // resolve provide after data/props
    // 调用created声明周期钩子函数
    /**
     * 调用 created 钩子函数
     */
    callHook(vm, 'created')

    // 结束初始化 性能度量
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    // 如果存在 el 选项,自动执行 $mount
    /**
     * 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount
     */
    if (vm.$options.el) {
      // 调用 $mount 方法,进入挂载阶段
      vm.$mount(vm.$options.el)
    }
  }
}

// 性能优化,打平配置对象上的属性,减少运行时原型链的查找,提高执行效率
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // 基于 构造函数 上的配置对象创建 vm.$options
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  // 有render函数,将其赋值到vm.$options
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

/**
 * 从构造函数上解析配置项
 */
export function resolveConstructorOptions (Ctor: Class<Component>) {
  // 从实例构造函数上获取选项
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    // 缓存基类的配置选项
    /**
     * 缓存
     */
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // 说明基类的配置项发生了更改
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // 找到更改的选项
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // 将更改的选项 和 extend 选项合并
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // 将新的选项赋值给 options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

/**
 * 解析构造函数选项中后续被修改或增加的选项
 */
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  // 构造函数选项
  const latest = Ctor.options
  // 密封的构造函数选项,备份
  const sealed = Ctor.sealedOptions
  // 对比两个选项,记录不一致的选项
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}

inject.js

代码语言:javascript复制
/* @flow */

import { hasOwn } from 'shared/util'
import { warn, hasSymbol } from '../util/index'
import { defineReactive, toggleObserving } from '../observer/index'

/**
 * 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
 */
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

/**
 * 解析 inject 选项
 * 1. 得到 { key: val } 形式的配置对象
 * 2. 对解析结果做响应式处理
 * @param {*} vm
 */
/**
 * 初始化 inject 配置项
 * 1.得到 result[key] = val
 * 2.对结果数据进行响应式处理,代理每个 key 到 vm 实例
 */
export function initInjections (vm: Component) {
  // 从配置项上解析inject选项,最后得到result[key]=val的结果
  /**
   * 解析 inject 配置项,然后从祖代组件的配置中找到,配置项中每一个 key 对应的 val,最后得到 result[key] = val 的结果
   */
  const result = resolveInject(vm.$options.inject, vm)
  /**
   * 对 result 做数据响应式处理,也有代理 inject 配置中每个 key 到 vm 实例的作用。
   * 不建议在子组件去更改这些数据,因为一旦祖代组件中 注入的 provide 发生更改,你在组件中做的更改就会被覆盖
   */
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be `  
            `overwritten whenever the provided component re-renders. `  
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        // 解析结果做响应式处理,将每个key代理到vue实例上
        // this.key
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

/**
 *
 * @param {*} inject = {
 *  key: {
 *   from: provideKey,
 *   default: xx
 *  }
 * }
 * @param {*} vm
 * @returns { key: val }
 */
/**
 * 解析 inject 配置项,从祖代组件的 provide 配置中找到 key 对应的值,否则用默认值,最后得到 result[key] = val
 * inject 对象肯定是以下这个结构,因为在 合并 选项时 对组件配置对象做了标准化处理
 * @param {*} inject = {
   key: {
     from: provideKey,
     default: xx
   }
 }
 */
export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    // inject 配置项的所有的 key
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    // 遍历 inject 选项中 key 组成的数组
    for (let i = 0; i < keys.length; i  ) {
      const key = keys[i]
      // #6574 in case the inject object is observed...
      // 跳过 __ob__ 对象
      if (key === '__ob__') continue
      // 获取 from 属性
      // 拿到 provide 中对应的 key 
      const provideKey = inject[key].from
      // 从祖代组件的配置项中找到 provide 选项,从而找到对应key的值
      let source = vm
      /**
       * 遍历所有的祖代组件,直到 根组件,找到 provide 中对应 key 的值,最后得到 result[key] = provide[provideKey]
       */
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          // result[key] = val
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      // 如果上一个循环未找到,则采用 inject[key].default,如果没有设置 default 值,则抛出错误
      if (!source) {
        // 设置默认值
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}

lifecycle.js

代码语言:javascript复制
/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import { mark, measure } from '../util/perf'
import { createEmptyVNode } from '../vdom/vnode'
import { updateComponentListeners } from './events'
import { resolveSlots } from './render-helpers/resolve-slots'
import { toggleObserving } from '../observer/index'
import { pushTarget, popTarget } from '../observer/dep'

import {
  warn,
  noop,
  remove,
  emptyObject,
  validateProp,
  invokeWithErrorHandling
} from '../util/index'

export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false

export function setActiveInstance(vm: Component) {
  const prevActiveInstance = activeInstance
  activeInstance = vm
  return () => {
    activeInstance = prevActiveInstance
  }
}

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
}

export function lifecycleMixin (Vue: Class<Component>) {
  // 组件初次渲染和更新的入口
  /**
   * 页面首次渲染和后续更新的入口位置,也是 patch 的入口位置
   */
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    // 页面的挂载点,真实的元素
    const prevEl = vm.$el
    // 老 VNode
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    // 新 VNode
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    // 首次渲染肯定不存在的
    if (!prevVnode) {
      // initial render
      // patch阶段,patch, diff算法
      /**
       * 老 VNode 不存在,表示首次渲染,即初始化页面时走这里
       */
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      /**
       * 响应式数据更新时,即更新页面时走这里
       */
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  /**
   * 直接调用 watcher.update 方法,迫使组件重新渲染。
   * 它仅仅影响组件实例本身和插入插槽内容的子组件,而不是所有子组件
   */
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  /**
   * 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令以及事件监听器。
   */
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      // 表示实例已经销毁
      return
    }
    // 调用 beforeDestroy 钩子
    callHook(vm, 'beforeDestroy')
    // 标识实例已经销毁
    vm._isBeingDestroyed = true
    // remove self from parent
    /**
     * 把自己从老爹($parent)的肚子里($children)移除
     */
    const parent = vm.$parent
    // 将自己从父组件的children属性中移除
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // watcher 移除
    // teardown watchers
    /**
     * 移除依赖监听
     */
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    // 调用 __patch__ ,销毁节点
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    // 调用 destroyed 钩子
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    // 关闭实例的所有事件监听
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

/**
 * 真正执行挂载的地方
 * @param {*} vm
 * @param {*} el
 * @param {*} hydrating
 * @returns
 */
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  /**
   * vm.$options.render 有两种来源:
   * 编译器将模板编译为 render 函数
   * 带编译器的 vue ,则在运行时编译模板为 render 函数,即 $mount 的第一步
   * 不带编译器的 vue,则由 vue-loader   vue-template-compiler 完成预编译,到运行时 vm.$options 上已经存在 render 了
   * 用户手动编写 render 函数
   */
  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
        )
      }
    }
  }
  // 调用 beforeMount 钩子函数
  callHook(vm, 'beforeMount')

  /**
   * 定义 updateComponent 方法,该方法在两个时间点会被调用
   * 1.初始化 watcher 时会被自动执行一次
   * 2.响应式数据更新时由 watcher 的 run 方法调用
   */
  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 = () => {
      // 执行 _update 进入更新阶段,首先执行 _render,将组件变成 VNode
      /**
       * 执行 vm._render() 函数,得到 虚拟 DOM,并将 vnode 传递给 _update 方法,接下来就该到 patch 阶段了
       */
      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
  /**
   * 实例化 组件 watcher
   */
  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
  /**
   * 调用 mounted 钩子函数
   */
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

export function updateChildComponent (
  vm: Component,
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode,
  renderChildren: ?Array<VNode>
) {
  if (process.env.NODE_ENV !== 'production') {
    isUpdatingChildComponent = true
  }

  // determine whether component has slot children
  // we need to do this before overwriting $options._renderChildren.

  // check if there are dynamic scopedSlots (hand-written or compiled but with
  // dynamic slot names). Static scoped slots compiled from template has the
  // "$stable" marker.
  const newScopedSlots = parentVnode.data.scopedSlots
  const oldScopedSlots = vm.$scopedSlots
  const hasDynamicScopedSlot = !!(
    (newScopedSlots && !newScopedSlots.$stable) ||
    (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
    (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
    (!newScopedSlots && vm.$scopedSlots.$key)
  )

  // Any static slot children from the parent may have changed during parent's
  // update. Dynamic scoped slots may also have changed. In such cases, a forced
  // update is necessary to ensure correctness.
  const needsForceUpdate = !!(
    renderChildren ||               // has new static slots
    vm.$options._renderChildren ||  // has old static slots
    hasDynamicScopedSlot
  )

  vm.$options._parentVnode = parentVnode
  vm.$vnode = parentVnode // update vm's placeholder node without re-render

  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode
  }
  vm.$options._renderChildren = renderChildren

  // update $attrs and $listeners hash
  // these are also reactive so they may trigger child update if the child
  // used them during render
  vm.$attrs = parentVnode.data.attrs || emptyObject
  vm.$listeners = listeners || emptyObject

  // update props
  if (propsData && vm.$options.props) {
    toggleObserving(false)
    const props = vm._props
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i  ) {
      const key = propKeys[i]
      const propOptions: any = vm.$options.props // wtf flow?
      props[key] = validateProp(key, propOptions, propsData, vm)
    }
    toggleObserving(true)
    // keep a copy of raw propsData
    vm.$options.propsData = propsData
  }

  // update listeners
  listeners = listeners || emptyObject
  const oldListeners = vm.$options._parentListeners
  vm.$options._parentListeners = listeners
  updateComponentListeners(vm, listeners, oldListeners)

  // resolve slots   force update if has children
  if (needsForceUpdate) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    vm.$forceUpdate()
  }

  if (process.env.NODE_ENV !== 'production') {
    isUpdatingChildComponent = false
  }
}

function isInInactiveTree (vm) {
  while (vm && (vm = vm.$parent)) {
    if (vm._inactive) return true
  }
  return false
}

export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i  ) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i  ) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, 'deactivated')
  }
}

/**
 * callHook(vm, 'mounted')
 * 执行实例指定的生命周期钩子函数
 * 如果实例设置有对应的 Hook Event,比如:<comp @hook:mounted="method" /> ,执行完 生命周期函数之后,触发该事件的执行
 * @param {*} vm 组件实例
 * @param {*} hook 生命周期钩子函数
 */
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  /**
   * 打开依赖收集
   */
  pushTarget()
  /**
   * 从实例配置对象中获取指定钩子函数,比如 mounted
   */
  const handlers = vm.$options[hook]
  // mounted hook
  const info = `${hook} hook`
  if (handlers) {
    // 通过 invokeWithErrorHandler 执行生命周期钩子
    for (let i = 0, j = handlers.length; i < j; i  ) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  /**
   * Hook Event ,如果设置了 Hook Event,比如 <comp @hook:mounted="method"/>,则通过 $emit 触发该事件
   * vm._hasHookEvent 标识组件是否有 hook event ,这是在 vm.$on 中处理组件自定义事件时设置的
   */
  if (vm._hasHookEvent) {
    // vm.$emit('hook:mounted')
    vm.$emit('hook:'   hook)
  }
  // 关闭依赖收集
  popTarget()
}

proxy.js

代码语言:javascript复制
/* not type checking this file because flow doesn't play well with Proxy */

import config from 'core/config'
import { warn, makeMap, isNative } from '../util/index'

let initProxy

if (process.env.NODE_ENV !== 'production') {
  const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,'  
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,'  
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,'  
    'require' // for Webpack/Browserify
  )

  const warnNonPresent = (target, key) => {
    warn(
      `Property or method "${key}" is not defined on the instance but `  
      'referenced during render. Make sure that this property is reactive, '  
      'either in the data option, or for class-based components, by '  
      'initializing the property. '  
      'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
      target
    )
  }

  const warnReservedPrefix = (target, key) => {
    warn(
      `Property "${key}" must be accessed with "$data.${key}" because `  
      'properties starting with "$" or "_" are not proxied in the Vue instance to '  
      'prevent conflicts with Vue internals. '  
      'See: https://vuejs.org/v2/api/#data',
      target
    )
  }

  const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

  if (hasProxy) {
    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
    config.keyCodes = new Proxy(config.keyCodes, {
      set (target, key, value) {
        if (isBuiltInModifier(key)) {
          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
          return false
        } else {
          target[key] = value
          return true
        }
      }
    })
  }

  const hasHandler = {
    has (target, key) {
      const has = key in target
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
      if (!has && !isAllowed) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }

  const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return target[key]
    }
  }

  initProxy = function initProxy (vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
}

export { initProxy }

render.js

代码语言:javascript复制
/* @flow */

import {
  warn,
  nextTick,
  emptyObject,
  handleError,
  defineReactive
} from '../util/index'

import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'

import { isUpdatingChildComponent } from './lifecycle'

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  /**
   * 定义 _c,它是 createElement 的一个柯里化方法
   * @param {*} a 标签名
   * @param {*} b 属性的 JSON 字符串
   * @param {*} c 子节点数组
   * @param {*} d 节点的规范化类型
   * @returns VNode or Array<VNode>
   */
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

export let currentRenderingInstance: Component | null = null

// for testing only
export function setCurrentRenderingInstance (vm: Component) {
  currentRenderingInstance = vm
}

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 在组件实例上挂载一些运行时需要用到的工具方法
  installRenderHelpers(Vue.prototype)

  // Vue.nextTick
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  // 执行组件的render函数,得到组件的vnode
  /**
   * 通过执行 render 函数生产 VNode
   * 不过里面加了大量的异常处理代码
   */
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // 获取 render
    // 用户实例化vue时提供了render配置项
    // 编译器 编译模板生成render
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    /**
     * 设置父 vnode。这使得渲染函数可以访问占位符节点上的数据。
     */
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      // 执行render函数得到组件的vnode
      /**
       * 执行 render 函数,生成 vnode
       */
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      /**
       * 到这儿,说明执行 render 函数时出错了
       * 开发环境渲染错误信息,生产环境返回之前的 vnode,以防止渲染错误导致组件空白
       */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    /**
     * 如果返回的 vnode 是数组,并且只包含了一个元素,则直接打平
     */
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // 多根节点的异常提示,vue2不支持多根节点
    // return empty vnode in case the render function errored out
    /**
     * render 函数出错时,返回一个空的 vnode
     */
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function '  
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}

state.js

代码语言:javascript复制
/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import Dep, { pushTarget, popTarget } from '../observer/dep'
import { isUpdatingChildComponent } from './lifecycle'

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

import {
  warn,
  bind,
  noop,
  hasOwn,
  hyphenate,
  isReserved,
  handleError,
  nativeWatch,
  validateProp,
  isPlainObject,
  isServerRendering,
  isReservedAttribute,
  invokeWithErrorHandling
} from '../util/index'

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

// 将key代理到vue实例上
/**
 * 设置代理,将 key 代理到 target 上
 */
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    // this._props.key
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  // 拦截 对 this.key 的访问
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 响应式原理的入口
/**
 * 两件事:
 * 数据响应式的入口:分别处理 props,methods,data,computed,watch
 * 优先级:props,methods,data,computed 对象中的属性不能出现重复,优先级和列出顺序一致
 * 其中 computed 中的 key 不能和 props,data 中的 key 重复,methods 不影响
 */
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 对 props 配置做响应式处理
  // 代理 props 配置上的key到vue实例,支持this.proKey的方式访问
  /**
   * 处理 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上
   */
  if (opts.props) initProps(vm, opts.props)
  // 判重处理,methods 对象中定义的属性不能和props对象中的属性重复,props优先级>methods的优先级
  // 将methods中的配置赋值到vue实例上,支持通过this.methodsKey的方式访问方法
  /**
   * 处理 methods 对象,校验每个属性的值是否为函数,和 props 属性比对进行判重处理
   * 最后得到 vm[key] = methods[key]
   */
  if (opts.methods) initMethods(vm, opts.methods)
  // 判重处理,data中的属性不能和props以及methods中的属性重复
  // 代理,将data中的属性代理到vue实例上,支持通过this.key的方式访问
  // 响应式
  /**
   * 做了三件事
   * 1.判重处理,data对象上的属性不能和 props,methods 对象上的属性相同
   * 2.代理 data 对象上的属性到 vm 实例
   * 3.为 data 对象上的数据设置响应式
   */
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // computed是通过watcher来实现的,对每个computedKey实例化一个watcher,默认懒执行
  // 将computedKey代理到vue实例上,支持通过this.computedKey的方式访问computed.key
  // 注意理解computed缓存的实现原理
  /**
   * 做了三件事:
   * 1.为 computed[key] 创建 watcher 实例,默认是懒执行
   * 2.代理 computed[key] 到 vm 实例
   * 3.判重,computed 中的 key 不能和 data,props 中的属性重复
   */
  if (opts.computed) initComputed(vm, opts.computed)
  // 核心:实例化一个watcher实例,并返回一个unwatch
  /**
   * 做了三件事:
   * 1.处理watch对象
   * 2.为每个 watch.key 创建 watcher 实例,key 和 watcher 实例可能是 一对多的关系
   * 3.如果设置了 immediate,则立即执行 回调函数
   */
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
  // computed 和 watch 有什么区别?
  // computed 默认懒执行,且不可更改,但是 watcher 可配置
  // 使用场景不同
  // computed同步操作, watcher异步操作

  /**
   * 其实到这里也能看出,computed 和 watch 在本质是没有区别的,都是通过 watcher 去实现的响应式
   * 非要说有区别,那也只是在使用方式上的区别,简单来说:
   * 1.watch:适用于当数据变化时执行异步或者开销较大的操作时使用,使用,即需要长事件等待的操作可以放在 watch 中
   * 2.computed:其中可以使用异步方法,但是没有任何意义。所以 computed 更适合做一些同步计算
   */
}

/**
 * 处理 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上
 */
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  /**
   * 缓存 props 的每个 key,性能优化
   */
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  // 遍历对象
  for (const key in propsOptions) {
    // 缓存key
    keys.push(key)
    // 获取 props[key] 的默认值
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be `  
            `overwritten whenever the parent component re-renders. `  
            `Instead, use a data or computed property based on the prop's `  
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 对 props 数据做响应式处理
      /**
       * 为 props 的每个 key 是设置数据响应式
       */
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    // 代理,this.propsKey
    if (!(key in vm)) {
      // 代理 key 到 vm 对象上
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

/**
 * 做了三件事
 * 1.判重处理,data 对象上的属性不能和 props,methods 对象上的属性相同
 * 2.代理 data 对象上的属性到 vm 实例
 * 3.为 data 对象上的数据设置响应式
 */
function initData (vm: Component) {
  // 得到 data 对象
  let data = vm.$options.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
    )
  }
  /**
   * 两件事
   * 1.判重处理,data 对象上的属性不能和 props,methods 对象上的属性相同
   * 2.代理 data 对象上的属性到 vm 实例
   */
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    // 判重处理,data中的属性不能和props以及methods中的属性重复
    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)) {
      // 代理 代理data中的属性到vue实例,支持通过this.key的方式访问
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式处理
  /**
   * 为 data 对象上的数据设置响应式
   */
  observe(data, true /* asRootData */)
}

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

const computedWatcherOptions = { lazy: true }

/**
 * 三件事:
 * 1.为 computed[key] 创建 watcher 实例,默认是懒执行
 * 2.代理 computed[key] 到 vm 实例
 * 3.判重,computed 中的 key 不能和 data,props 中的属性重复
 *
 @param {*} computed = {
   key1: function() { return xx },
   key2: {
     get: function() { return xx },
     set: function(val) {}
   }
 }
 */
function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  // 遍历 computed 对象
  for (const key in computed) {
    /**
     * 获取key对应的值,即 getter 函数
     */
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      // 实例化一个watcher,所以computed其实就是通过watcher来实现的
      /**
       * 为 computed 属性创建 watcher 实例
       */
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        // 配置项, computed 默认是懒执行
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      /**
       * 代理 computed 对象中的属性到 vm 实例
       * 这样就可以使用 vm.computedKey 访问计算属性
       */
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      // 非生产环境有一个判重处理,computed 对象中的属性不能和 data, props 中的属性相同
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

/**
 * 代理 computed 对象中的 key 到 target (vm) 上
 */
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  // 构造属性描述符(get,set)
  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
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 将computed配置项中的key代理到vue实例上,支持通过this.computedKey的方式去访问computed中的属性
  /**
   * 拦截对 target.key 的访问和设置
   */
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

/**
 * @returns 返回一个函数,这个函数在访问 vm.computedProperty 时会被执行,然后返回执行结果
 */
function createComputedGetter (key) {
  /**
   * computed 属性值会缓存的原理也是在这里结合 watcher.dirty、watcher.evaluate、watcher.update 实现的
   */
  return function computedGetter () {
    // 拿到 watcher
    /**
     * 得到当前 key 对应的 watcher
     */
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 执行watcher.evaluate方法
      // 执行computed.key的值(函数)得到函数的执行结果,赋值给watcher.value
      // 将watcher.dirty置为false
      // computed和methods有什么区别?
      // 一次渲染当中,只执行一次computed函数,后续的访问就不会再执行了,直到下一次更新之后,才会再次执行
      /**
       * 计算 key 对应的值,通过执行 computed.key  的回调函数来得到
       * watcher.dirty 属性就是大家常说的 computed 计算结果会缓存的原理
       * <template>
       *  <div>{{ computedProperty }}</div>
       *  <div>{{ computedProperty }}</div>
       * </template>
       * 像这种情况下,在页面的一次渲染中,两个 dom 中的 computedProperty 只有第一个
       * 会执行 computed.computedProperty 的回调函数计算实际的值
       * 即执行 watcher.evaluate,而第二个就不走计算过程了
       * 因为上一次执行 watcher.evalute 时把 watcher.dirty 置为了 false
       * 待页面更新后, watcher.update 方法会将 watcher.dirty 重新置为 true
       * 供下次页面更新时重新计算 computed.key 的结果
       */
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

/**
 * 功能同 createComputedGetter 一样
 */
function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

/**
 * 做了以下三件事,其实最关键的就是第三件事情
 * 1.校验 methods[key],必须是一个函数
 * 2.判重
 * methods 中的 key 不能和 props 中的 key 相同
 * methods 中的 key 与 Vue 实例上已有的方法重叠,一般是一些内置方法,比如 $ 和 _ 开头的方法
 * 将 methods[key] 放到 vm 实例上,得到
 */
function initMethods (vm: Component, methods: Object) {
  // 获取 props 配置项
  const props = vm.$options.props
  // 判重,methods中的key不能和props中的key重复
  // props优先级大于methods
  /**
   * 遍历 methods 对象
   */
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. `  
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. `  
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    // 将methods中的所有方法赋值到vue实例上,支持通过 this.methodKey 的方式访问定义的方法
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

/**
 * 处理 watch 对象的入口,做了两件事:
 * 1.遍历 watch 对象
 * 2.调用 createWatcher 函数
 @param {*} watch = {
   'key1': function(val, oldVal) {},
   'key2': 'this.methodName',
   'key3': {
     handler: function(val, oldVal) {},
     deep: true
   },
   'key4': [
     'this.methodName',
     function handler1() {},
     {
       handler: function() {},
       immediate: true
     }
   ],
   'key.key5': {...}
 }
 */
function initWatch (vm: Component, watch: Object) {
  // 遍历 watch 配置项
  /**
   * 遍历 watch 对象
   */
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      // handler 为数组,遍历数组,获取其中的每一项,然后调用 createWatcher
      for (let i = 0; i < handler.length; i  ) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

/**
 * 两件事:
 * 1.兼容性处理,保证 handler 肯定是一个函数
 * 2.调用 $watch
 * @returns
 */
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  // 如果是对象,从hanlder属性中获取函数
  /**
   * 如果 handler 为对象,则获取其中的 handler 选项的值
   */
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  // 如果是字符串,表示的是一个methods方法,直接通过this.methodsKey的方式拿到这函数
  /**
   * 如果 handler 为字符串,则说明是一个 methods 方法,获取 vm[handler]
   */
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  // 处理data数据,定义get方法,访问this._data
  const dataDef = {}
  dataDef.get = function () { return this._data }
  // 处理props数据
  const propsDef = {}
  propsDef.get = function () { return this._props }
  // 异常提示
  if (process.env.NODE_ENV !== 'production') {
    // this.$data = {} or new Val
    // this.$data.newProperty = new val
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. '  
        'Use nested data properties instead.',
        this
      )
    }
    // 你设置它的时候,直接告诉你 props 是只读的
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  // 将 $data 和 $props 挂载到Vue原型链,支持通过this.$data 和this.$props的方式访问
  /**
   * 将 data 属性 和 props 属性挂载到 Vue.prototype 对象上
   * 这样在程序中就可以通过 this.$data 和 this.$props 来访问 data 和 props 对象了
   */
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  // this.$set 和 this.$delete
  // Vue.set 和 Vue.delete的一个别名
  Vue.prototype.$set = set
  Vue.prototype.$delete = del

/**
 * 创建 watcher ,返回 unwatch,共完成如下 5 件事:
 * 1.兼容性处理,保证最后 new Watcher 时的 cb 为函数
 * 2.标示用户 watcher
 * 3.创建 watcher 实例
 * 4.如果设置了 immediate,则立即执行一次 cb
 * 5.返回 unwatch
 * @param {*} expOrFn key
 * @param {*} cb 回调函数
 * @param {*} options 配置项,用户直接调用 this.$watch 时可能会传递一个 配置项
 * @returns 返回 unwatch 函数,用于取消 watch 监听
 */
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    // 处理cb是对象的情况,保证后续处理中cb肯定是一个函数
    /**
     * 兼容性处理,因为用户调用vm.$watch时设置的 cb 可能是对象
     */
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    // options.user 表示用户 watcher,还有渲染watcher,即 updateComponent 方法中实例化的 watcher
    options = options || {}
    // 标记,这是一个用户watcher
    options.user = true
    // 实例化watcher
    /**
     * 创建 watcher
     */
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 存在immediate:true,则立即执行回调函数
    /**
     * 如果用户设置了 immediate 为 true,则立即执行一次回调函数
     * 
     try {
       cb.call(vm, watcher.value)
     } catch (error) {
       handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
     }
     */
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    // 返回 一个unwatch 调用这个方法teardown
    /**
     * 返回一个 unwatch 函数,用于接触监听
     */
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

0 人点赞