一、概述
我们平时在搭建Vue项目时都是用官网提供的脚手架进行搭建,但对于底层逻辑的东西很少了解,为了帮助大家更深入了解Vue底层原理【快智岛】专门开设了【从源码开始学Vue专栏】和大家一起开启学Vue之旅。
二、渐进式框架
Vue.js 设计的初衷就包括可以被渐进式地采用,下面就渐进式框架结合实际项目给大家加以说明:
将 Vue.js 添加到项目中主要有四种方式
三、了解Vue的引入原理
我们在了解Vue的时候,首先要了解Vue是从哪里来,我们在搭建好的脚手架里面,通过main.js可以找见引入import Vue from 'vue',从从node_modules中找见vue文件夹,进入package.json文件,这个文件的作用是描述整个项目的。在这个文件中存在两个配置字段,它们都是程序的主入口文件。
{
...
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
"unpkg": "dist/vue.js",
"jsdelivr": "dist/vue.js",
"typings": "types/index.d.ts",
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
],
...
在module的优先级大于main的优先级。在module不存在时,main对应的配置项就是主入口文件。可以看到dist/vue.runtime.esm.js才是主入口文件。在脚手架main.js中,我们可以找见创建Vue实例的代码如下所示:
import Vue from 'vue'
import App from './App.vue'
import router from './router/'
import store from './store/'
import i18n from './locales/'
const vm = new Vue({
router,
store,
i18n,
created: bootstrap,
render: h => h(App)
}).$mount('#app')
export default vm
在dist/vue.runtime.esm.js文件中搜索Vue的定义函数如下:
function Vue (options) {
//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);
}
四、了解Vue的初始化函数
通过上述描述,初始画函数包含在 this._init(options);中,这里的this指的是Vue实例
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
var startTag, endTag;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = "vue-perf-start:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
}
// a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
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;
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);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
五、render函数
render: h => h(App)是ES6的写法,其实就是如下内容的简写:
# ES5写法
render: function (createElement) {
return createElement(App);
}
# ES6写法
render: createElement => createElement(App)
# h代替createElement
render: h => h(App)
官方文档中是这样的,createElement 是 Vue.js 里面的 函数,这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
render: function (createElement) {
return createElement(
'h' + this.level, // tag name 标签名称
this.$slots.default // 子组件中的阵列
)
}
为什么会使用h代替createElement
It comes from the term "hyperscript", which is commonly used in many virtual-dom implementations. "Hyperscript" itself stands for "script that generates HTML structures" because HTML is the acronym for "hyper-text markup language". 它来自单词 hyperscript,这个单词通常用在 virtual-dom 的实现中。Hyperscript 本身是指
生成HTML 结构的 script 脚本,因为 HTML 是 hyper-text markup language 的缩写(超文本标记语言) 也就是说,createElement 函数是用来生成 HTML DOM 元素的,而上文中的 Hyperscript也是用来创建HTML结构的脚本,这样作者才把 createElement 简写成 h。
而 createElement(也就是h)是vuejs里的一个函数。这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
其实在vue 1.x 中,这样的写法也就是如下的含义:
new Vue({
el: '#app',
template:'</App>'
componets: {App}
})
然后页面中使用:
<div id='app'>
<app></app>
</div>
其实在vue 2.x 中,这样的写法也就是如下的含义:
const vm = new Vue({
render: h => h(App)
}).$mount('#app')
然后页面中使用:
<div id='app'>
</div>