vue源码分析-2-Vue相关原型方法

388 阅读2分钟

src/core/instance/index.js

通过概述分析得知,Vue源码最先执行的就src/core/instance/index.js文件,此文件定义了Vue的构造方法

此文件主要定义了许多Vue相关原型方法,主要调用以下方法给Vue的原型链扩展方法,接下来我们一一分析

// 定义初始化相关方法
initMixin(Vue)
// 定义状态相关方法
stateMixin(Vue)
// 事件相关
// $on$off$once方法
eventsMixin(Vue)
// 生命周期相关
lifecycleMixin(Vue)
// 实现$nextTick 与 _render方法
renderMixin(Vue)
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'

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')
  }
  this._init(options)
}

// 定义初始化相关方法
initMixin(Vue)
// 定义状态相关方法
stateMixin(Vue)
// 事件相关
// $on$off$once方法
eventsMixin(Vue)
// 生命周期相关
lifecycleMixin(Vue)
// 实现$nextTick 与 _render方法
renderMixin(Vue)

export default Vue

initMixin(Vue)

主要定义了Vue.prototype._init方法,new Vue()实例化的时候调用的方法就是_init()方法

_init()方法将在 new Vue(options) 一节说明

stateMixin(Vue)

此方法主要定义了Vue.$set , Vue.$delete , Vue.$watch方法, 这三个方法的主要作用都是为了实现响应式原理,这些方法将在 响应式原理 章节讲解

eventsMixin(Vue)

此方法主要定义了 Vue.prototype.$on, Vue.prototype.$once, Vue.prototype.$off, Vue.prototype.$emit四个方法

  • Vue.prototype.$on方法作用是给vue实例vm._events 赋值,组件绑定的事件都存在_events(数组)属性上
  • Vue.prototype.$once方法的作用就是执行过一次的方法,就会被移除
  • Vue.prototype.$off方法的作用就是将事件从_events(数组)属性中移除
  • Vue.prototype.$emit方法的作用就是可以执行保存在_events属性中的事件
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/

  // 监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 如果是数组,递归调用
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // vm的_events属性保存事件,同名的事件都保存在一个数组里
      (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.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      // 执行on()的话,on会先被移除,再执行一次,也就是执行一次之后会被移除
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    // 无参数,移除所有事件
    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
    }
    // 如果fn参数没传,或者为false,移除所有事件
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    // 如果fn参数传了,只移除于fn相等的函数
    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.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      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}".`
        )
      }
    }
    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
  }
}

lifecycleMixin(Vue)

主要定义了三个方法

  • Vue.prototype._update 更新渲染真实dom
  • Vue.prototype.$forceUpdate 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
  • Vue.prototype.$destroy 销毁组件方法

详细介绍将在后续章节讲解

renderMixin(Vue)

主要定义了两个方法
Vue.prototype.$nextTick方法, 此方法将在专门章节讲解 Vue.prototype._render方法, 此方法将在渲染章节讲解

接下来执行的是initGlobalAPI(Vue)全局api定义