vue2-实例方法与全局API的实现(一)

在vue源码中/src/core/instance/index.js 会有实例的初始化代码

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)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

其中定义了vue构造函数,然后分别调用了initMixin, stateMixin, eventsMixin,lifecycleMixin,renderMixin 这五个函数,然后将vue构造函数作为参数传递给这个五个函数。

这个五个函数的作用 就是向Vue 的原型中挂载方法。

初始化方法 initMixin

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

export function initMixin (Vue: Class) {
  Vue.prototype._init = function (options?: Object) {
...
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
...

可以看到当 initMixin被调用是,Vue构造函数的prototype属性会被添加_init方法。 在new Vue()会调用this._init()方法,实现一系列初始化操作。

数据相关的实例方法

在stateMixin被调用是,会向 vue构造函数 的prototype属性挂载与数据相关的实例方法。 vm.$watch, vm.$set, vm.$delete. 并且会挂载上 $data$props

state.js

...
import {
  set,
  del,
  observe,
  defineReactive,
  toggleObserving
} from '../observer/index'
...
export function stateMixin (Vue: Class) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
...

事件相关的实例方法

在 eventsMixn中 会实现 vm.$on vm.$off vm.$once vm.$emit这个四个方法。

export function eventsMixin (Vue: Class) {
  Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
  Vue.prototype.$once = function (event: string, fn: Function): Component {
  Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
  Vue.prototype.$emit = function (event: string): Component {
...
}

vm.$on的实现

使用: vm.$on(event, callback)
参数:

  • {String | Array}event
  • {Function} callback
用户: 监听实例上的自定义事件,事件由$emit触发。回调函数会接受所有传入事件 所触发的函数的 额外参数
const hookRE = /^hook:/
  // vm.$on(event, callback)
  // 监听实例上的自定义事件,事件由$emit触发
  Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
    const vm: Component = this
    // 如果是参数是数组  遍历数组 ,每一项都递归调用vm.$on,使回调 可以被 数组中 每项事件名对应的 事件列表 监听
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // 不是数组 判断 vm._events中是否存在,不存在 设置为空数组 然后传入回调
   // _events是一个对象,对象上每一个 key(事件名)对应的都是 一个数组 ,数组中是监听的事件
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // ???todo
      // 而不是哈希查找hook:event cost 使用在注册时标记的布尔标志
      // 而不是散列查找
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

vm.$off的实现

使用 vm.$off([event, callback])
参数:

  • {String | Array} event
  • {Function} callback

用法: 移除自定义事件监听器

  • 如果没有传递参数, 则移除所有的事件监听器
  • 如果只传了事件名,则移除该事件所有的 事件监听器
  • 如果都传了,移除对应这个回调的监听器监听器

其中 移除对应回调的事件监听器, 用的是倒序循环移除,好处是不会改变未被移除事件的位置

Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
    const vm: Component = this
    // 如果没有传参数 ,移除所有的事件监听器
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // 数组,遍历每一项 递归调用 vm.$off 移除对应的监听器
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // 不是数组 获取 当前对应的 需要移除监听器的数组
    // vm._evnets是一个对象  
    const cbs = vm._events[event]
    // 如果 不存在,说明没有被 监听过,直接返回实例
    if (!cbs) {
      return vm
    }
    // 如果 没有传入需要移除的 监听器,那么移除该事件所有的监听器
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // 存在需要移除的监听事件 
    // 倒序循环  移除对应的事件
    // 倒序的好处是 splice之后 ,不会影响未被遍历到的监听器位置
    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.$once

使用 vm.$once(event, callbakc)

参数:

  • {String | Arrray} event
  • {Function} callback

用法: 监听一个自定义事件,但是只触发一次,触发之后移除监听器

具体实现其实就是 在vm.on 实现监听自定义事件,当自定义事件触发后,执行拦截器,将监听器移除

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    // 包装 fn事件, 调用的时候 ,移除自身。并执行 fn函数
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    // 把fn函数挂载到 on事件上
    // vm.$off取消监听时 会判断 cb===fn || cb.fn === fn
    on.fn = fn
    // 监听 包装后的on 事件
    vm.$on(event, on)
    return vm
  }

vm.$emit

使用: vm.$emit(event, [...args])

参数:

  • {String} event
  • [...args]

用法: 触发当前实例上的事件,附加参数会传给监听器回调

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) {
      // toArray 把类似于数组的数据转换为真正的数组
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      // toArray 把类似于数组的数据转换为真正的数组, 它的第二个参数是起始位置
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      // 遍历调用 invokeWithErrorHandling 处理监听器
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }


function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    // 根据是否有参数 ,执行监听器的回调
    res = args ? handler.apply(context, args) : handler.call(context)

    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // 避免在嵌套调用时多次触发catch
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }
  return res
}

生命周期相关的实例方法

与生命周期相关的实例方法有4个, 分别是 vm.$mount, vm.$forceUpdate, vm.$nextTick, vm.$destroy

其中lifecycleMixin中 挂载到 vue 实例的有vm.$forceUpdate,vm.$destroy

vm.$forceUpdate
作用是 是vue实例重新渲染,仅仅影响实例本身以及插入插槽内容的子组件,而不是 所有子组件。
vue的每一个实例都有一个watcher, 当状态发生变化是,会通知组件级别,然后组件内使用虚拟DOM进行更详细的渲染操作。

原理就是手动通知vue实例重新渲染

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    // 如果实例有 watcher 则 手动 触发 watcher的update方法 重新渲染
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

vm.$destory

// 销毁实例
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    // 防止 被重复调用  如果_isBeingDestroyed为true,则正在销毁中或者已经被销毁了
    if (vm._isBeingDestroyed) {
      return
    }
    // 触发 beforeDestroy 的钩子函数
    callHook(vm, 'beforeDestroy')
    // 设置_isBeingDestroyed 为ture 标识正在销毁中
    vm._isBeingDestroyed = true
    // 移除当前组件与 父组件的连接 (父组件中删除 自己)
    const parent = vm.$parent
    // 父组件存在 && 父组件没有被销毁 && 不是抽象组件
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }

     // vm._watcher 是vue实例上的,监听组件中的所有状态(状态的依赖列表)
    // 从所有依赖项的Dep 列表中 将自己移除 (从其他dep中移除当前实例的watcher)
    // 移除之后,当状态发生变化时,watcher实例就不会再得到通知了   
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    // vm._watchers 是用户调用vm.$watche创建的 的依赖列表
    let i = vm._watchers.length
    while (i--) {
      // 也跳跃watcher实例的teardown 移除自身
      vm._watchers[i].teardown()
    }
    // ???todo
    // 从数据对象中删除引用
    // 冻结对象可能没有观察者。
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // 添加_isDestroyed 属性,标识vue实例已经被销毁
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    // 不会将已经渲染到页面的DOM节点移除,但是会把模板中的所有指令解绑
    vm.__patch__(vm._vnode, null)
    // 触发destroyed钩子
    callHook(vm, 'destroyed')
    // 移除所有的事件监听器
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // todo??? 发布循环引用
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

vm.$nexttick
这里需要知道宏任务和微任务的区别。
然后

// 标志  使用微任务
export let isUsingMicroTask = false

// 需要 执行的 函数
const callbacks = []
// 是否 nexttick执行中 只能有一个pending 
let pending = false
...
// nextTick 多次使用 ,callbacks中会有多个回调放到 微任务或宏任务中 等待执行,
// pending 控制 nextTick只会执行一次,执行完之后callbacks 置空,pending 重置
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // callbacks中推入 自定义 函数 ()=>{}
  callbacks.push(() => {
    // 如果有 回调
    if (cb) {
      try {
        // 执行自定义函数时 ,执行 回调
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      // 如果没有传回调 ,执行自定义函数的时候, 执行 _resolve  (即下面 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then)
      _resolve(ctx)
    }
  })
  // 如果pending 为false, 可以执行
  // timerFunc 会返回一个 promise.then微任务 ,挂起不会执行 ,当调用nextTick的那一层 宏任务执行完,才 会执行timerFunc 里面的微任务 。 而这个微任务 执行 一个函数 flushCallbacks ,flushCallbacks把callback里面的回调全部执行并清空
  if (!pending) {
    // pending 为true , 执行中。(只能有一个nextTicke)
    pending = true
    // 执行 nextTick处理的微任务或者宏任务
    timerFunc()
  }
  // 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

看一下timerFunc

// 需要执行的 任务
let timerFunc

// 如果有 支持Promise , timerFunc 返回 为微任务 
// 执行到微任务会挂起 ,不会立即执行 ,等待当前宏任务或微任务执行完之后,才会执行 挂起的微任务
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // ios 中 回调被推送到微任务队列中,但队列没有被刷新,直到浏览器需要执行其他一些工作时
    // 添加一个空计时器来“强制”刷新微任务队列。
    if (isIOS) setTimeout(noop)
  }
  // 微任务标识 为true
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  // 非 ie  且 支持 MutationObserver(提供了监视对DOM树所做更改的能力)
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 微任务 MutationObserver
  let counter = 1
  // 当观察到变动时执行的回调函数 flushCallbacks
  const observer = new MutationObserver(flushCallbacks)
  // 选择需要观察变动的节点
  const textNode = document.createTextNode(String(counter))
  // observe 配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
  // {characterData: true} 观察器的配置(需要观察什么变动) 
  observer.observe(textNode, {
    characterData: true
  })
  // 执行 timerFunc 时, 修改节点, 节点的变通会 通知回调函数 flushCallbacks执行
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 微任务标识 为true
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 上面不支持 返回宏任务 setImmediate(比setTimeout更好)
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  // 都不支持 返回宏任务 用 setTimeout 
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

微任务真正执行的列表

// 执行 nexttick传入的函数
function flushCallbacks () {
  // 重置pending 
  pending = false
  // 拷贝 callbacks 后置空
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

next-tick.js源码

/* @flow */
/* globals MutationObserver */

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

// 标志  使用微任务
export let isUsingMicroTask = false

// 需要 执行的 函数
const callbacks = []
// 是否 nexttick执行中 只能有一个pending 
let pending = false

// 执行 nexttick传入的函数
function flushCallbacks () {
  // 重置pending 
  pending = false
  // 拷贝 callbacks 后置空
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

// 需要执行的 任务
let timerFunc

// 如果有 支持Promise , timerFunc 返回 为微任务 
// 执行到微任务会挂起 ,不会立即执行 ,等待当前宏任务或微任务执行完之后,才会执行 挂起的微任务
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // ios 中 回调被推送到微任务队列中,但队列没有被刷新,直到浏览器需要执行其他一些工作时
    // 添加一个空计时器来“强制”刷新微任务队列。
    if (isIOS) setTimeout(noop)
  }
  // 微任务标识 为true
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  // 非 ie  且 支持 MutationObserver(提供了监视对DOM树所做更改的能力)
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 微任务 MutationObserver
  let counter = 1
  // 当观察到变动时执行的回调函数 flushCallbacks
  const observer = new MutationObserver(flushCallbacks)
  // 选择需要观察变动的节点
  const textNode = document.createTextNode(String(counter))
  // observe 配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
  // {characterData: true} 观察器的配置(需要观察什么变动) 
  observer.observe(textNode, {
    characterData: true
  })
  // 执行 timerFunc 时, 修改节点, 节点的变通会 通知回调函数 flushCallbacks执行
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 微任务标识 为true
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 上面不支持 返回宏任务 setImmediate(比setTimeout更好)
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  // 都不支持 返回宏任务 用 setTimeout 
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

// nextTick 多次使用 ,callbacks中会有多个回调放到 微任务或宏任务中 等待执行,
// pending 控制 nextTick只会执行一次,执行完之后callbacks 置空,pending 重置
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // callbacks中推入 自定义 函数 ()=>{}
  callbacks.push(() => {
    // 如果有 回调
    if (cb) {
      try {
        // 执行自定义函数时 ,执行 回调
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      // 如果没有传回调 ,执行自定义函数的时候, 执行 _resolve  (即下面 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then)
      _resolve(ctx)
    }
  })
  // 如果pending 为false, 可以执行
  // timerFunc 会返回一个 promise.then微任务 ,挂起不会执行 ,当调用nextTick的那一层 宏任务执行完,才 会执行timerFunc 里面的微任务 。 而这个微任务 执行 一个函数 flushCallbacks ,flushCallbacks把callback里面的回调全部执行并清空
  if (!pending) {
    // pending 为true , 执行中。(只能有一个nextTicke)
    pending = true
    // 执行 nextTick处理的微任务或者宏任务
    timerFunc()
  }
  // 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

你可能感兴趣的:(vue2-实例方法与全局API的实现(一))