vue源码分析(四) 实例属性和方法

10/4/2018 vue源码分析面试

# 1. 概述

​ 在上一章——vue源码分析(三) 整体架构 (opens new window),中我们分析 Vue 的构造函数时,忽略了一些初始化函数,接下来我们将一一分析如下的几个初始化函数。

​ 源码目录: src/core/instance/index.js

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

initMixin(Vue) // 初始化Vue
stateMixin(Vue) // 数据绑定,$watch方法
eventsMixin(Vue) // 初始化事件绑定方法
lifecycleMixin(Vue) // 初始化vue 生命周期: 更新 销毁 
renderMixin(Vue) // 初始化渲染的函数
1
2
3
4
5
6
7
8
9
10
11

# 2. initMixin

​ 首先看看 initMixin 定义,如下:

源码目录: `src/core/instance/init.js`
/**
 * 初始化vue
 * @param {Vue构造器} Vue 
 */
export function initMixin (Vue: Class<Component>) {
  //初始化函数
  Vue.prototype._init = function (options?: Object) {
    /* */
  }
}
1
2
3
4
5
6
7
8
9
10

initMixin 方法的主要作用就是给 Vue 实例上添加一个 _init 方法。

说明:关于 _init 方法具体的源码已经在 vue源码分析(三) 整体架构 (opens new window) 章节中的 初始化 部分有详细分析,请移步查看!

# 3. stateMixin

​ 接下来我们看一下 stateMixin 函数的定义,如下:

​ 源码目录: src/core/instance/state.js

/**
 * 数据绑定,$watch方法
 * @param {vue构造器} Vue 
 */
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.
  const dataDef = {}
  // 重新定义get 和set方法
  dataDef.get = function () { return this._data } // 获取data中的数据
  const propsDef = {}
  propsDef.get = function () { return this._props } // 获取props 数据
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () { //避免替换实例根$data,使用嵌套数据属性代替
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () { //props 只是可读的数据不可以设置更改
      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 (
    expOrFn: string | Function, // 监听的属性
    cb: any, // 监听的属性对应的函数
    options?: Object //参数
  ): Function { /* */ }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

stateMixin 主要是在 Vue.prototype 添加一下一些属性和方法:

  1. $data 属性:Vue 实例观察的数据对象,代理是 _data 这个实例属性,是一个只读属性。
  2. $props 属性:当前组件接收到的 props 对象,代理是 _props 这个实例属性,是一个只读属性。
  3. $set 方法:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。
  4. $delete方法:删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。
  5. $watch 方法:观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。

说明:关于 ES5Object.defineProperty 方法,我们这里不做详细说明,可以移步至这里 (opens new window)学习。

# 4. eventsMixin

​ 接下来我们再分析 eventsMixin 函数,先看一下的它的定义,如下:

​ 源码目录: src/core/instance/events.js

/**
 * 初始化事件绑定方法
 * @param {Vue构造函数} Vue 
 */
export function eventsMixin (Vue: Class<Component>) {
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { /* */ }

  Vue.prototype.$once = function (event: string, fn: Function): Component { /* */ }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { /* */ }

  Vue.prototype.$emit = function (event: string): Component { /* */ }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

stateMixin 这个方法也是在 Vue.prototype 添加一些方法,主要有:

  1. $on 方法:监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。
  2. $once 方法:监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
  3. $off 方法:移除自定义事件监听器。
  4. $emit 方法:触发当前实例上的事件。附加参数都会传给监听器回调。

# 5. lifecycleMixin

​ 接着就是 lifecycleMixin 函数,老规矩我们还是先从函数的定义开始,代码如下:

​ 源码目录: src/core/instance/lifecycle.js

/**
 * 初始化vue 更新/销毁
 * @param {Vue构造器} Vue 
 */
export function lifecycleMixin (Vue: Class<Component>) {
  /**
   * 将vnode转换成dom,渲染在视图中
   * @param {vnode dom虚拟节点} vnode 
   * @param {布尔类型的参数是跟ssr相关} hydrating 
   */
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { /* */ }

  // 更新观察者数据
  Vue.prototype.$forceUpdate = function () { /* */ }

  // 销毁组建周期函数
  Vue.prototype.$destroy = function () { /* */ }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

​ 同上,lifecycleMixin 这个方法也是在 Vue.prototype 添加一些方法,主要有:

  1. _update 方法:将 vnode 转换成 dom,渲染在视图中。
  2. $forceUpdate 方法:迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
  3. $destroy 方法:完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。触发 beforeDestroydestroyed 的钩子。

# 6. renderMixin

​ 最后是 renderMixin 函数,我们先看一下它的定义,如下:

​ 源码目录: src/core/instance/render.js

/**
 * 初始化渲染的函数
 * @param {Vue构造器} Vue 
 */
export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 给 Vue 原型上添加_x(例如_l)这样的函数
  installRenderHelpers(Vue.prototype)

  // 给 Vue 原型上添加 $nextTick API
  Vue.prototype.$nextTick = function (fn: Function) { /* */ }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

​ 这个方法一开始调用了 installRenderHelpers 函数,我们先分析这个函数的主要作用,首先也是从函数的定义入手,如下:

​ 源码目录: src/core/instance/render-helpers/index.js

/**
 * 安装渲染助手
 * @param {参数} target 
 */
export function installRenderHelpers (target: any) {
  // 实际上,这意味着使用唯一键将节点标记为静态。* 标志 v-once. 指令
  target._o = markOnce
  // 字符串转数字,如果失败则返回字符串
  target._n = toNumber
  // 将对象或者其他基本数据变成一个字符串
  target._s = toString
  // 根据value 判断是数字,数组,对象,字符串,循环渲染
  target._l = renderList
  // 用于呈现<slot>的运行时帮助程序 创建虚拟slot vonde
  target._t = renderSlot
  // 检测a和b的数据类型,是否是不是数组或者对象,对象的key长度一样即可,数组长度一样即可
  target._q = looseEqual
  // arr数组中的对象,或者对象数组是否和val 相等
  target._i = looseIndexOf
  // 用于呈现静态树的运行时助手,创建静态虚拟vnode
  target._m = renderStatic
  // 用于解析过滤器的运行时助手
  target._f = resolveFilter
  // 检查两个key是否相等,如果不想等返回true 如果相等返回false
  target._k = checkKeyCodes
  // 用于将v-bind="object"合并到VNode的数据中的运行时助手,检查value 是否是对象,并且为value 添加update 事件
  target._b = bindObjectProps
  // 创建一个文本节点 vonde
  target._v = createTextVNode
  // 创建一个节点为空的vnode
  target._e = createEmptyVNode
  // 解决作用域插槽,把对象数组事件分解成对象
  target._u = resolveScopedSlots
  // 判断value 是否是对象,并且为数据 data.on 合并data和value 的on 事件
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

​ 以上代码就是 installRenderHelpers 函数的源码,可以发现,这个函数的作用就是在 Vue.prototype 上添加一系列方法。

说明:这些方法的作用已经在源码注释中有说明具体实现这里不做详细分析,后面都会讲解到。

​ 最后又在也是在 Vue.prototype 添加了 $nextTick_render 两个方法。

# 7. 其他

​ 我们再回到 initGlobalAPI(Vue) 被调用的文件中,这个函数被调用完,有执行了一些代码,如下:

​ 源码目录: src/core/index.js

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

​ 这里也是在 Vue.prototype 上分别添加了两个只读的属性 $isServer$ssrContext

​ 最后,在 Vue 构造函数上添加了一个静态属性 version,存储了当前 Vue 的版本号。

# 8. 总结

​ 通过我们对上面五个方法的分析,我们可以总结出 Vue.prototype 即 Vue实例上面具体有哪些实例属性和方法,接下来我们已思维导图的方式总结。

说明:在本章节中没有遇到的实例属性和方法我们也会在以后的分析总归纳进来,会在思维导图中标在出来具体的章节。

vue实例属性和方法