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的关系的主要内容,如果未能解决你的问题,请参考以下文章