Vue 源码解读(6)—— 实例方法

前言

上一篇文章 Vue 源码解读(5)—— 全局 API 详细介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会详细介绍各个实例方法的实现原理。

目标

深入理解以下实例方法的实现原理。

  • vm.$set

  • vm.$delete

  • vm.$watch

  • vm.$on

  • vm.$emit

  • vm.$off

  • vm.$once

  • vm._update

  • vm.$forceUpdate

  • vm.$destroy

  • vm.$nextTick

  • vm._render

源码解读

入口

/src/core/instance/index.js

该文件是 Vue 实例的入口文件,包括 Vue 构造函数的定义、各个实例方法的初始化。

// Vue 的构造函数
function Vue (options) {
  // 调用 Vue.prototype._init 方法,该方法是在 initMixin 中定义的
  this._init(options)
}

// 定义 Vue.prototype._init 方法
initMixin(Vue)
/**
 * 定义:
 *   Vue.prototype.$data
 *   Vue.prototype.$props
 *   Vue.prototype.$set
 *   Vue.prototype.$delete
 *   Vue.prototype.$watch
 */
stateMixin(Vue)
/**
 * 定义 事件相关的 方法:
 *   Vue.prototype.$on
 *   Vue.prototype.$once
 *   Vue.prototype.$off
 *   Vue.prototype.$emit
 */
eventsMixin(Vue)
/**
 * 定义:
 *   Vue.prototype._update
 *   Vue.prototype.$forceUpdate
 *   Vue.prototype.$destroy
 */
lifecycleMixin(Vue)
/**
 * 执行 installRenderHelpers,在 Vue.prototype 对象上安装运行时便利程序
 * 
 * 定义:
 *   Vue.prototype.$nextTick
 *   Vue.prototype._render
 */
renderMixin(Vue) 

vm. d a t a 、 v m . data、vm. datavm.props

src/core/instance/state.js

这是两个实例属性,不是实例方法,这里简单介绍以下,当然其本身实现也很简单

// data
const dataDef = {}
dataDef.get = function () { return this._data }
// props
const propsDef = {}
propsDef.get = function () { return this._props }
// 将 data 属性和 props 属性挂载到 Vue.prototype 对象上
// 这样在程序中就可以通过 this.$data 和 this.$props 来访问 data 和 props 对象了
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef) 

vm.$set

/src/core/instance/state.js

Vue.prototype.$set = set 

set

/src/core/observer/index.js

/**
 * 通过 Vue.set 或者 this.$set 方法给 target 的指定 key 设置值 val
 * 如果 target 是对象,并且 key 原本不存在,则为新 key 设置响应式,然后执行依赖通知
 */
export function set (target: Array | 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)}`)
  }
  // 更新数组指定下标的元素,Vue.set(array, idx, val),通过 splice 方法实现响应式更新
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 更新对象已有属性,Vue.set(obj, key, val),执行更新即可
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // 不能向 Vue 实例或者 $data 添加动态添加响应式属性,vmCount 的用处之一,
  // this.$data 的 ob.vmCount = 1,表示根组件,其它子组件的 vm.vmCount 都是 0
  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
  }
  // target 不是响应式对象,新属性会被设置,但是不会做响应式处理
  if (!ob) {
    target[key] = val
    return val
  }
  // 给对象定义新属性,通过 defineReactive 方法设置响应式,并触发依赖更新
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
} 

vm.$delete

/src/core/instance/state.js

Vue.prototype.$delete = del 

del

/src/core/observer/index.js

/**
 * 通过 Vue.delete 或者 vm.$delete 删除 target 对象的指定 key
 * 数组通过 splice 方法实现,对象则通过 delete 运算符删除指定 key,并执行依赖通知
 */
export function del (target: Array | 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)}`)
  }

  // target 为数组,则通过 splice 方法删除指定下标的元素
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__

  // 避免删除 Vue 实例的属性或者 $data 的数据
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 如果属性不存在直接结束
  if (!hasOwn(target, key)) {
    return
  }
  // 通过 delete 运算符删除对象的属性
  delete target[key]
  if (!ob) {
    return
  }
  // 执行依赖通知
  ob.dep.notify()
} 

vm.$watch

/src/core/instance/state.js

/**
 * 创建 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
  // 兼容性处理,因为用户调用 vm.$watch 时设置的 cb 可能是对象
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options)
  }
  // options.user 表示用户 watcher,还有渲染 watcher,即 updateComponent 方法中实例化的 watcher
  options = options || {}
  options.user = true
  // 创建 watcher
  const watcher = new Watcher(vm, expOrFn, cb, options)
  // 如果用户设置了 immediate 为 true,则立即执行一次回调函数
  if (options.immediate) {
    try {
      cb.call(vm, watcher.value)
    } catch (error) {
      handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
    }
  }
  // 返回一个 unwatch 函数,用于解除监听
  return function unwatchFn() {
    watcher.teardown()
  }
} 

vm.$on

/src/core/instance/events.js

const hookRE = /^hook:/
/**
 * 监听实例上的自定义事件,vm._event = { eventName: [fn1, ...], ... }
 * @param {*} event 单个的事件名称或者有多个事件名组成的数组
 * @param {*} fn 当 event 被触发时执行的回调函数
 * @returns 
 */
Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    // event 是有多个事件名组成的数组,则遍历这些事件,依次递归调用 $on
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn)
    }
  } else {
    // 将注册的事件和回调以键值对的形式存储到 vm._event 对象中 vm._event = { eventName: [fn1, ...] }
 

你可能感兴趣的:(前端,Vue,前端)