1.1 Vue的引入
Vue
的使用按照官方的说法支持CDN
和NPM
两种方式,CDN
的方式是以script
的方式将打包好的vue.js
引入页面脚本中,而NPM
的方式是和诸如 webpack
或 Browserify
模块打包器配置使用,以npm install vue
的方式引入,这也是我们开发应用的主要形式。而从单纯分析源码思路和实现细节的角度来讲,打包后的vue.js
在分析和提炼源码方面会更加方便,所以这个系列的源码分析,使用的是打包后的vue
脚本,版本号是v2.6.8
1.1.1 基础使用
分析的开始当然是vue
的基础使用,我们引入了vue.js
并且new
了一个Vue
实例,并将它挂载到#app
上,这是最基础的用法。
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
message: '选项合并'
},
})
</script>
虽然这一节的重点是阐述Vue
的选项配置,从选项配置入手也是我们从零开始品读源码最容易开始的思路,但是为了分析的完整性,避免后续出现未知的概念,有必要先大致了解一下vue
在脚本引入之后分别做了什么。
1.1.2 Vue构造器
打包后的源码是遵从UMD
规范的,它是commonjs
和amd
的整合。而Vue
的本质是一个构造器,并且它保证了只能通过new
实例的形式去调用,而不能直接通过函数的形式使用。
(function (global, factory) {
// 遵循UMD规范
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
···
// Vue 构造函数
function Vue (options) {
// 保证了无法直接通过Vue()去调用,只能通过new的方式去创建实例
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
return Vue
})
1.1.3 定义原型属性方法
Vue之所以能适应基础的开发场景,除了经常提到的支持组件化开发,以及完善的响应式系统等外,还有重要的一点是它提供了丰富的api
方法,不管是静态还是原型方法,它们都丰富到足以满足我们日常基础的开发需求。所以熟练阅读vue-api文档并精准使用api
方法是迈向熟练开发的前提。接下来我们看看这些方法属性是在哪里定义的,注意,该小节会忽略大部分属性方法具体的实现,这些详细的细节会贯穿在后续系列的分析中。
首先是原型上的属性方法,在构造函数的定义之后,有这样五个函数,他们分别针对不同场景定义了Vue
原型上的属性和方法。
// 定义Vue原型上的init方法(内部方法)
initMixin(Vue);
// 定义原型上跟数据相关的属性方法
stateMixin(Vue);
//定义原型上跟事件相关的属性方法
eventsMixin(Vue);
// 定义原型上跟生命周期相关的方法
lifecycleMixin(Vue);
// 定义渲染相关的函数
renderMixin(Vue);
我们一个个看,首先initMixin
定义了内部在实例化Vue
时会执行的初始化代码,它是一个内部使用的方法。
function initMixin (Vue) {
Vue.prototype._init = function (options) {}
}
stateMixin
方法会定义跟数据相关的属性方法,例如代理数据的访问,我们可以在实例上通过this.$data
和this.$props
访问到data,props
的值,并且也定义了使用频率较高的this.$set,this.$delte
等方法。
function stateMixin (Vue) {
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
// 代理了_data,_props的访问
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
// $set, $del
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
// $watch
Vue.prototype.$watch = function (expOrFn,cb,options) {};
}
eventsMixin
会对原型上的事件相关方法做定义,文档中提到的vm.$on,vm.$once,vm.$off,vm.$emit
也就是在这里定义的。
function eventsMixin(Vue) {
// 自定义事件监听
Vue.prototype.$on = function (event, fn) {};
// 自定义事件监听,只触发一次
Vue.prototype.$once = function (event, fn) {}
// 自定义事件解绑
Vue.prototype.$off = function (event, fn) {}
// 自定义事件通知
Vue.prototype.$emit = function (event, fn) {
}
lifecycleMixin,renderMixin
两个都可以算是对生命周期渲染方法的定义,例如$forceUpdate
触发实例的强制刷新,$nextTick
将回调延迟到下次 DOM
更新循环之后执行等。
// 定义跟生命周期相关的方法
function lifecycleMixin (Vue) {
Vue.prototype._update = function (vnode, hydrating) {};
Vue.prototype.$forceUpdate = function () {};
Vue.prototype.$destroy = function () {}
}
// 定义原型上跟渲染相关的方法
function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {};
// _render函数,后面会着重讲
Vue.prototype._render = function () {};
}
1.1.4 定义静态属性方法
除了原型方法外,Vue
还提供了丰富的全局api
方法,这些都是在initGlobalAPI
中定义的。
/* 初始化构造器的api */
function initGlobalAPI (Vue) {
// config
var configDef = {};
configDef.get = function () { return config; };
{
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
// 通过Vue.config拿到配置信息
Object.defineProperty(Vue, 'config', configDef);
// 工具类不作为公共暴露的API使用
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive###1
};
// Vue.set = Vue.prototype.$set
Vue.set = set;
// Vue.delete = Vue.prototype.$delete
Vue.delete = del;
// Vue.nextTick = Vue.prototype.$nextTick
Vue.nextTick = nextTick;
// 2.6 explicit observable API
Vue.observable = function (obj) {
observe(obj);
return obj
};
// 构造函数的默认选项默认为components,directive,filter, _base
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
// options里的_base属性存储Vue构造器
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
// Vue.use()
initUse(Vue);
// Vue.mixin()
initMixin$1(Vue);
// 定义extend扩展子类构造器的方法
// Vue.extend()
initExtend(Vue);
// Vue.components, Vue.directive, Vue.filter
initAssetRegisters(Vue);
}
看着源码对静态方法的定义做一个汇总。
- 为源码里的
config
配置做一层代理,可以通过Vue.config
拿到默认的配置,并且可以修改它的属性值,具体哪些可以配置修改,可以先参照官方文档。 - 定义内部使用的工具方法,例如警告提示,对象合并等。
- 定义
set,delet,nextTick
方法,本质上原型上也有这些方法的定义。 - 对
Vue.components,Vue.directive,Vue.filter
的定义,这些是默认的资源选项,后续会重点分析。 - 定义
Vue.use()
方法 - 定义
Vue.mixin()
方法 - 定义
Vue.extend()
方法
现在我相信你已经对引入Vue
的阶段有了一个大致的认识,在源码分析的初期阶段,我们不需要死磕每个方法,思路的实现细节,只需要对大致的结构有基本的认识。有了这些基础,我们开始进入这个章节的主线。