Vue源码之Vue和VueComponent的关系

Posted 天地会珠海分舵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码之Vue和VueComponent的关系相关的知识,希望对你有一定的参考价值。

在这个专栏前面的好几篇文章中都有提到过Vue实例对象和组件VueComponent实例对象,说它们两个是非常类似的两个对象,但是并没有进一步探讨它们具体的一些实现。

今天想花点时间把这方面的知识给补充补充。

1. Vue对象的使用例子

我们接触Vue这个类的使用,一般都是在main.js中开始的。我们的第一个,通常也是唯一的一个Vue实例对象就是在这里创建的,而整个Vue应用的生命周期也是从这里开始的。

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;
const vm = new Vue(
  render: (h) => h(App),
  components:  App ,
  data: ,
).$mount("#app");

这里new Vue调用的就是Vue构造函数。里面传入的就是我们此前分析过的渲染函数,data选项,应用的组件等选项信息。

2. Vue构建函数

Vue构造函数在源码路径的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);

export default Vue;

构造函数很简单,就是调用了Vue原型的_init方法,并把我们前面传入的选项参数传递进去。

但是,这个_init是在什么时候加入到Vue的prototype原型对象中的呢?其实就是系统在加载这个index.js的文件的时候,而不是特别的某个调用。

3. Vue.prototype原型初始化

一旦我们的项目引入了vue.js,然后就会对自动执行这个js的文件,期间引用到这个文件,所以就会自动的执行initMixin这些函数。

而这一系列的方法调用其实就是设置Vue的prototype上的原型方法,比如上面提到的_init方法:

export function initMixin(Vue: Class<Component>) 
  Vue.prototype._init = function (options?: Object) 
    const vm: Component = this;
    // a uid
    vm._uid = uid++;

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

    ...

    if (vm.$options.el) 
      // debugger;
      vm.$mount(vm.$options.el);
    
  ;


这里就不一一列举和分析prototype上的那一堆方法了,我们今后用到哪个就会分析哪个,这里先从高层建瓴的角度知道prototype上的那些属性和方法是怎么来的就行了。

也正是有了这一系列函数所实现的Vue原型的初始化,才让我们能够很方便的通过this.$nextTick这些原型方法来做事情。

那么我们看了Vue示例对象的初始化的大概流程,下面我们看下组件的初始化流程大概是怎么样的。

4. Vue组件的自动创建

我们通常写组件的时候,都只是在一个vue单文件组件文件中直接编写。然后再用到的地方将其import进来,然后在components选项中注册该组件,最后在模板中通过组件名称来作为标签进行使用就完了,期间并没有见到有显式的组件创建过程。

事实上,Vue组件的创建过程是框架默默帮我们做了。

我们知道页面的渲染在vue中要经历两个过程

  • 首先是调用render函数生成虚拟DOM
  • 然后调用update方法根据新的虚拟DOM和老的虚拟DOM做diff,将diff的内容上树即更新页面。

详情可参考此前的两篇文章

当然,第一次初始化的时候是没有老的虚拟DOM的,比如前面分析的Vue实例的初始化,在碰到这个标签对应的虚拟DOM的时候,如果发现是一个没有别实例化的组件,就会直接调用VueComponent构造函数来创建。

5. VueComponent和extend方法的关系

其实,VueComponent构造函数同时也是是一个高阶函数,因为它是通过调用Vue.extend方法得到的。

Vue.extend = function (extendOptions: Object): Function 
  extendOptions = extendOptions || ;
  const Super = this;
  ...

  const Sub = function VueComponent(options) 
    this._init(options);
  ;
  ...
  return Sub;
;

下面我们先看一个通过extend方法获取到VueComponent构造函数创建一个组件的例子。

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="vue.js"></script>
    <style>
      button 
        font-size: 2rem;
        padding: 1rem;
      
    </style>
  </head>
  <body>
    <div id="app">
      <hello-world></hello-world>
    </div>

    <script>
      const HelloWorld = Vue.extend(
        template: `
        <div>
          <div>Hello World</div>
        </div>
        `,
        data() 
          return ;
        ,
      );
      const vm = new Vue(
        el: "#app",
        components: 
          HelloWorld,
        ,
      );
    </script>
  </body>
</html>

例子中我们通过Vue.extend方法的调用,得到的其实就是VueComponent的构造函数。通过下面的调试输出也能说明这一点

6. VueComponent和Vue的一个重要关系

这里我们再次看下Vue的extend方法

Vue.extend = function (extendOptions: Object): Function 
  extendOptions = extendOptions || ;
  const Super = this;

  ...

  const Sub = function VueComponent(options) 
    debugger;
    this._init(options);
  ;
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;
  
  ...

  return Sub;
;

因为这里extend方法是Vue的成员方法,所以里面的this指向是Vue, 也就是这里的Super是Vue。

跟着就是把Vue的构造函数赋予给Sub。

紧跟着就是我们这里的一个关键点,首先,Object.create(a)的作用是创建一个对象,该对象的__proto__指向参数a。

所以这一行的意义就是,创建一个对象,将其__proto__指向Super.prototype,即Vue.prototype,然后将这个对象赋值给Sub,即VueComponent的prototype。

程序员的语言来说就是: VueComponent.prototype.proto === Vue.prototype。可别忘了,原型对象毕竟是个对象,也是有隐式原型的。只是平常我们见到的往往会指向Object,而这里,我们将VueComponent的原型对象的隐式原型指向了Vue的原型对象。

最终的结果就是,所有Vue的原型对象prototype上有的属性和方法,在VueComponent中顺着原型链去找都能找到。而我们上面分析的Vue可以看到,Vue本身没有定义多少方法和属性,大部分的方法如$nextTick,$watch等都是定义在原型对象prototype上的。

我是@天地会珠海分舵,「青葱日历」和「三日清单」作者。能力一般,水平有限,觉得我说的还有那么点道理的不妨点个赞关注下!

以上是关于Vue源码之Vue和VueComponent的关系的主要内容,如果未能解决你的问题,请参考以下文章

Vue基础系列(十八)vuecomponent - 重要的内置关系

Vue组件基础(组件的嵌套VueComponent)

Vue组件基础(组件的嵌套VueComponent)

Vue组件基础(组件的嵌套VueComponent)

Vue 学习总结笔记

Vue2.0—Vue与Component的关系