前言

自己学习Vue也有不少时间了,特别是喜欢研究一些原理,一直以来都是看别人的博客学习,这次想分享学习的心得,而且这也是我第一次发博客,写的不好请见谅,文章随缘更新(最近有时间),感谢观看

注:一定要搭配源码看,版本为:2.6.10

在new Vue之前的初始化

我们先来到`node_modules\vue\src\core\instance\index.js`, 这里是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'// 这里是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')  }  this._init(options)}// 首先会进行一系列的初始化initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue

接下来会一步步分析, initMixin(Vue), stateMixin(Vue), eventsMixin(Vue), lifecycleMixin(Vue), renderMixin(Vue)

  • initMixin(Vue)

    文件位置: node_modules\vue\src\core\instance\init.js

    export function initMixin (Vue: Class<Component>) {  Vue.prototype._init = function (options?: Object) {      // ...具体逻辑后续讲解  }}

    很明显,在Vue.prototype上初始化了一个_init方法, 它是在我们new Vue后进行了调用, 至于具体实现后面详解。

  • stateMixin(Vue)

    文件位置: node_modules\vue\src\core\instance\state.js

    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 = {}  // 定义一个data空对象, 后面就会知道,这是对$data属性的特性设置  dataDef.get = function () { return this._data }  // 定义data的getter方法  const propsDef = {}  propsDef.get = function () { return this._props }// 定义props的getter方法  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 (    expOrFn: string | Function,    cb: any,    options?: Object  ): Function {    const vm: Component = this    if (isPlainObject(cb)) {      return createWatcher(vm, expOrFn, cb, options)    }    options = options || {}    options.user = true    const watcher = new Watcher(vm, expOrFn, cb, options)    if (options.immediate) {      try {        cb.call(vm, watcher.value)      } catch (error) {        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)      }    }    return function unwatchFn () {      watcher.teardown()    }  }}

    接下来我们一句句分析这个函数做了什么

    首先创建了两个dataDef,propsDef对象,并且设置了get属性访问器,返回了_data_props,至于具体作用在下面会有说明

      const dataDef = {}  dataDef.get = function () { return this._data }  const propsDef = {}  propsDef.get = function () { return this._props }

    接下来是这段代码,如果不是生产环境,也就是开发环境,我们是不能直接赋值给$data$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)        }    } // this.$data = ... ,this.$props  这样都是会报警告的

    在原型上添加属性访问器,也就是我们可以在组件内直接使用this.$data访问

      // 在原型中添加$data属性,并设置访问器,返回的是_data  Object.defineProperty(Vue.prototype, '$data', dataDef)   // 在原型中添加$props属性,并设置访问器,返回的是_props  Object.defineProperty(Vue.prototype, '$props', propsDef)

    我们来看下这个$data到底是什么

    <template>  <div class="about">    <h1>{{ appName }}</h1>    <h1>{{ username }}</h1>  </div></template><script>export default {  data() {    return {      count: 1    }  },  mounted() {    // 打印了this    console.log(this.$data);  }};</script>

    打印结果很明显,就是vue实例的data方法返回的对象,并且包含__ob__属性,表示它被观测了,这些我们以后会在数据响应会详细讲解

    {__ob__: Observer}count: 1__ob__: Observer {value: {…}, dep: Dep, vmCount: 1}get count: ƒ reactiveGetter()set count: ƒ reactiveSetter(newVal)__proto__:

    接下来我们继续进行,它在Vue原型上添加了两个方法,set和del,那么他们到底是用来做什么的呢,我们直接定位到这两个方法的实现,并且下面两个代码实现我将以注释的形式讲解,请跳转到响应的源代码

      // 原型上添加 $set和$delete方法  Vue.prototype.$set = set  Vue.prototype.$delete = del
    // src/core/observer/index.js // @param {*} target 目标对象// @param {*} key 要添加的键 // @param {*} val 要添加的值export function set (target: Array<any> | Object, key: any, val: any): any {    // 如果目标对象是undefined或者null 或者是原始类型就警告  if (process.env.NODE_ENV !== 'production' &&    (isUndef(target) || isPrimitive(target))  ) {    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)  }  // 如果目标是数组, 并且key是有效的索引,也就是说必须是整数  // 整个if所干的事情就是,删除原来的值,并替换新的值,然后直接返回新的值  if (Array.isArray(target) && isValidArrayIndex(key)) {    // 就找到最大的那个长度    target.length = Math.max(target.length, key)    // 如果key对应的下标有值,就删掉数组下标对应的值,然后添加新的值,否则不会删除,添加新的值    target.splice(key, 1, val) // 注意这里的splice是变异后的方法,会触发响应    return val // 直接返回属性的值  }  // 如果key是对象的key,并且不是系统原生的属性,比如说一些toString,之类的存在于Object.prototype原型的属性  if (key in target && !(key in Object.prototype)) {    // 直接改变目标对象的属性值,并返回,注意改变值会触发响应,如果页面有使用到这个属性,就会自动改变页面的这个值    target[key] = val    return val   }  // 获取__ob__对象  const ob = (target: any).__ob__  // 这句话的意思就是,避免给Vue实例添加响应式的属性或者给他的$data  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  }  // 如果key属性不在目标对象上,也就是说本来就不存在于目标对象上,也就意味着,不是响应式的  // 当我们赋值后,会自动添加响应式,并触发页面更新  if (!ob) {    target[key] = val    return val  }  // ob.value设置为响应式  defineReactive(ob.value, key, val)  // 通知页面更新  ob.dep.notify()  return val}

    总结下 Vue.prototype.$set所做的事情,其实就是向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性(官方文档原话)。

    接下来我们再来看看$del实现

    export function del (target: Array<any> | 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)}`)  }  if (Array.isArray(target) && isValidArrayIndex(key)) {    target.splice(key, 1)    return  }  const ob = (target: any).__ob__  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 target[key]  if (!ob) {    return  }  ob.dep.notify()}

    我想不用多说,根据上面的下面不难看出,就是删除响应式数据中的一个属性,并且更新视图。

    官方文档介绍:删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。

    然后我们回到初始化时候的代码

      Vue.prototype.$set = set  Vue.prototype.$delete = del  // 添加$watch方法  Vue.prototype.$watch = function (    expOrFn: string | Function,    cb: any,    options?: Object  ): Function {      // ...后续  }

    以上代码又是在原型上添加了一个$watch方法,简单说一下,其实就是监听一个数据的变化,在以后的数据响应会详细讲解

    回到最开始我们看到的四个初始化方法,我们继续分析

    initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)
  • eventsMixin(Vue)

    文件位置:node_modules\vue\src\core\instance\events.js

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

    分别在Vue.prototype, 初始化了四个方法$on,$once, $off, $emit,这些代码的实现我们暂且不看,以后都会有讲解,不过我们还是讲一下具体作用

    • vm.$on: 监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。
    • vm.$emit: 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
    • vm.$off: 移除自定义事件监听器。
    • vm.$once: 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。

      以上方法,有个常见的使用场景:eventBus,我们可以再次new Vue()一个实例,专门用来父子组件直接的传值

lifecycleMixin(Vue)

文件位置: node_modules\vue\src\core\instance\lifecycle.js

  export function lifecycleMixin (Vue: Class<Component>) {    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {      // ..    }      Vue.prototype.$forceUpdate = function () {      const vm: Component = this      if (vm._watcher) {        vm._watcher.update()      }    }      Vue.prototype.$destroy = function () {     // ..    }  }

Vue.prototype上初始化_update, $forceUpdate, $destroy三个方法

  • _update:用于渲染dom或者更新dom到视图
  • $forceUpdate:迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
  • $destroy:完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。

renderMixin(Vue)

文件位置:node_modules\vue\src\core\instance\render.js

export function renderMixin (Vue: Class<Component>) {  // install runtime convenience helpers  installRenderHelpers(Vue.prototype)  Vue.prototype.$nextTick = function (fn: Function) {    return nextTick(fn, this)  }  Vue.prototype.$nextTick = function (): VNode {    // ...    return vnode  }}

Vue.prototype上初始化$nextTick

  • $nextTick: 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

initGlobalAPI

我们定位到src\core\global-api\index.js文件,Vue除了初始化上面的一些实例方法,也添加了下面这些静态方法

export function initGlobalAPI (Vue: GlobalAPI) {  // config  const configDef = {}  configDef.get = () => config  if (process.env.NODE_ENV !== 'production') {    configDef.set = () => {      warn(        'Do not replace the Vue.config object, set individual fields instead.'      )    }  }  Object.defineProperty(Vue, 'config', configDef)  // exposed util methods.  // NOTE: these are not considered part of the public API - avoid relying on  // them unless you are aware of the risk.  Vue.util = {    warn,    extend,    mergeOptions,    defineReactive  }  Vue.set = set  Vue.delete = del  Vue.nextTick = nextTick  // 2.6 explicit observable API  Vue.observable = <T>(obj: T): T => {    observe(obj)    return obj  }  Vue.options = Object.create(null)  ASSET_TYPES.forEach(type => {    Vue.options[type + 's'] = Object.create(null)  })  // this is used to identify the "base" constructor to extend all plain-object  // components with in Weex's multi-instance scenarios.  Vue.options._base = Vue  // 这里把keep-alive组件注册到全局  extend(Vue.options.components, builtInComponents)  initUse(Vue)  initMixin(Vue)  initExtend(Vue)  initAssetRegisters(Vue)}

我们来看下具体做了哪些事情

  const configDef = {}    configDef.get = () => config  if (process.env.NODE_ENV !== 'production') {      // 无法更改config,否则警告    configDef.set = () => {      warn(        'Do not replace the Vue.config object, set individual fields instead.'      )    }  }// 添加配置对象  Object.defineProperty(Vue, 'config', configDef) // 添加了`util`对象,里面有一些方法,`warn:用于警告`,`extend:合并两个对象`,`mergeOptions:合并选项`,`defineReactive:设置响应式数据`  Vue.util = {    warn,    extend,    mergeOptions,    defineReactive  }  Vue.set = set  Vue.delete = del  Vue.nextTick = nextTick  // 这三个不必多说和之前的初始化一样
Vue.observable = <T>(obj: T): T => {   observe(obj)   return obj}// 深度观察对象,并设置为响应式
      // ASSET_TYPES    // export const ASSET_TYPES = [    //   'component',    //   'directive',    //   'filter'    // ]    Vue.options = Object.create(null)    ASSET_TYPES.forEach(type => {      Vue.options[type + 's'] = Object.create(null)    })  // 在Vue的options属性上添加一些属性,初始为空对象,包括我们常见的components,directives,filter  // 是不是很眼熟
 Vue.options._base = Vue //  Vue.options添加了_base属性指向Vue,此时的options如下  /*  Vue.options = {      components:{},      directives:{},      filter:{},      _base: Vue  }  */
extend(Vue.options.components, builtInComponents)

builtInComponents这个又是什么鬼?我们来看看是什么

// src/core/components/index.jsimport KeepAlive from './keep-alive'export default {  KeepAlive}

原来是把KeepAlive组件添加到了components

接下来又是各种初始化,注意千万别把之前的初始化混淆

  initUse(Vue)               添加了use方法,一般用来注册插件  initMixin(Vue)           添加了mixin方法,混入全局的钩子函数  initExtend(Vue)           使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象  initAssetRegisters(Vue)   注册全局组件,指令或过滤器
  • initUse: 位置src/core/global-api/use.js,添加了use方法,一般用来注册插件
  • initMixin:位置src/core/global-api/mixin.js,添加了mixin方法,混入全局的钩子函数
  • initExtend:位置src/core/global-api/extend.js,使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象

    // 样例var Profile = Vue.extend({  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',  data: function () {    return {      firstName: 'Walter',      lastName: 'White',      alias: 'Heisenberg'    }  }})
  • initAssetRegisters:位置src/core/global-api/assets.js,注册全局组件,指令或过滤器

    Vue.component('my-component', { /* ... */ })Vue.directive('my-directive', {  bind: function () {},  inserted: function () {},  update: function () {},  componentUpdated: function () {},  unbind: function () {}})Vue.filter('my-filter', function (value) {  // 返回处理后的值})

经过了initGlobalAPI(Vue)最终返回了Vue,其他代码暂且不看,主要用来处理服务端渲染

import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype, '$isServer', {  get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', {  get () {    /* istanbul ignore next */    return this.$vnode && this.$vnode.ssrContext  }})Object.defineProperty(Vue, 'FunctionalRenderContext', {  value: FunctionalRenderContext})Vue.version = '__VERSION__'export default Vue

总结

经过了initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue), Vue.prototype有哪些方法呢

Vue.prototype = {    _init: f,  // new Vue初始化时调用    $data: get => _data      $props: get => _props,      $set: f,     $delete: f,    $watch: f,  // 监听data数据变化的方法    $on: f,  // 事件方法    $once: f, // 事件方法    $emit: f, // 手动触发事件方法    _update: f, // 初始化或者更新,把VNode渲染为真正的DOM节点    $forceUpdate: f, //     $destroy: f}

经过initGlobalAPI添加了许多静态方法和对象

  1.Vue.util = {    warn,    extend,    mergeOptions,    defineReactive  }  2.Vue.set = set  3.Vue.delete = del  4.Vue.nextTick = nextTick  5.Vue.observable = <T>(obj: T): T => {   observe(obj)   return obj  }  6.Vue.options = Object.create(null)  7.Vue.options[type + 's'] = Object.create(null)  8.创建全局的keep-alive组件  9.initUse(Vue)               添加了use方法,一般用来注册插件  10.initMixin(Vue)           添加了mixin方法,混入全局的钩子函数  11.initExtend(Vue)           使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象  12.initAssetRegisters(Vue)   注册全局组件,指令或过滤器  

下一篇:new Vue()后发生了什么