Vue2源码解析(二)

445 阅读2分钟

微信公众号:  [大前端驿站]
关注大前端驿站。问题或建议,欢迎公众号留言。 这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

我们在上一章vue2源码解析(一)简单介绍了Vue的执行流程,本章我们创建一个例子来看一下挂载前的处理流程。 还是之前的编码环境下,我们在examples目录下新建一个my-test目录,然后创建一个init.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>test Vue</title>
  <script src="../../dist/vue.js"></script>
</head>
<body>
  <div id="app" @click="add">{{counter}}</div>
</body>
<script>
  const app = new Vue({
    el: '#app',
    data: function() {
      return {
        counter: 1
      }
    },
    methods: {
      add() {
        this.counter++ 
      }
    }
  }).$mount()
</script>
</html>

我们通过这个例子来看下Vue的初始化的流程,首先new了一个Vue实例,那我们通过源码可以定位到instance目录下的index.js中,可以找到Vue的构造方法

src\core\instance\index.js

...
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)
...

构造方法中触发了一个_init方法,此时我们发现这个_init方法有点难找,但是同时可以看到构造方法下面又多个Mixin的方法,并且都是通过传入Vue作为参数。

我们先来看看initMixin之外的其他几个Mixin做了哪些事情

  • stateMixin: 给Vue原型对象设置属性$data、$props、$set、$del
  • eventsMixin: 给Vue原型对象设置属性$on、$once、$off、$emit
  • lifecycleMixin: 给Vue原型对象设置属性_update、$forceUpdate、$destroy、$emit
  • renderMixin: 给Vue原型对象设置属性$nextTick、_render

由此可见这些Mixin都是起到丰富Vue的功能的作用,这些原型对象上的方法或者属性大多是我们在日常工作中用过的,这里就不贴代码细讲,同学们可以逐个去过一遍这部分代码。

然后我们重点分析一下initMixin做了哪些工作。在initMixin中就可以看到给Vue的原型对象设置了_init,构造方法正是触发了这个方法。

src\core\instance\init.js

  Vue.prototype._init = function (options?: Object) {
    // merge options 第一步工作合并选项
    if (options && options._isComponent) {
      ...
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    // 第二步工作是设置代理
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 接下来是一系列处理options的工作
    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')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 最后触发$mount方法执行挂载的操作
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

合并选项

处理组件实例与父组件实例或者根实例的选项合并,还有我们用到mixin方式传入的选项,需要先进行选项的合并,然后将合并后的选项赋值给实例的$options属性

设置代理

这一步不要理解为响应式的处理,很多同学看到Proxy就以为是响应式的处理。那这里设置的代理是为什么的,我们回到init.html的例子中

...
add() {
  this.counter++ 
}
...

我们都知道add方法中这个this是指向的Vue实例,那么为什么这里可以直接this.counter这么拿到设置在data属性里面的counter呢,这里就是这一步代理的作用,这里将选项里设置的data里面的属性都代理到Vue实例上面。

处理options

  • initLifecycle(vm):定义vm:$parent、$root、$children、$refs等
  • initEvents(vm):初始化事件:_events、_hasHookEvent,如果有父组件传入的事件更新到本组件上
  • initRender(vm):初始化render相关的属性,并且给$attrs和$listeners属性做响应式处理
  • callHook(vm, 'beforeCreate'):触发beforeCreate钩子函数
  • initInjections(vm):处理Injections注入的属性
  • initState(vm):依次处理props、methods、data、computed、watch等选项
  • initProvide(vm):处理Provide提供给后代组件的属性
  • callHook(vm, 'created'):触发created钩子函数

触发$mount方法

接下来就开始进入编译和挂载的阶段了,这部分我们下一章再细讲

~~

感谢观看!未完待续......

关注下方【大前端驿站】
让我们一起学,一起进步

【分享、点赞、在看】三连吧,让更多的人加入我们~~