一、概述
我们平时在搭建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>