1.1 Vue的引入

Vue的使用按照官方的说法支持CDNNPM两种方式,CDN的方式是以script的方式将打包好的vue.js引入页面脚本中,而NPM的方式是和诸如 webpackBrowserify 模块打包器配置使用,以npm install vue的方式引入,这也是我们开发应用的主要形式。而从单纯分析源码思路和实现细节的角度来讲,打包后的vue.js在分析和提炼源码方面会更加方便,所以这个系列的源码分析,使用的是打包后的vue脚本,版本号是v2.6.8

1.1.1 基础使用

分析的开始当然是vue的基础使用,我们引入了vue.js并且new了一个Vue实例,并将它挂载到#app上,这是最基础的用法。

  1. <div id="app"></div>
  2. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script>
  3. <script>
  4. var vm = new Vue({
  5. el: '#app',
  6. data: {
  7. message: '选项合并'
  8. },
  9. })
  10. </script>

虽然这一节的重点是阐述Vue的选项配置,从选项配置入手也是我们从零开始品读源码最容易开始的思路,但是为了分析的完整性,避免后续出现未知的概念,有必要先大致了解一下vue在脚本引入之后分别做了什么。

1.1.2 Vue构造器

打包后的源码是遵从UMD规范的,它是commonjsamd的整合。而Vue的本质是一个构造器,并且它保证了只能通过new实例的形式去调用,而不能直接通过函数的形式使用。

  1. (function (global, factory) {
  2. // 遵循UMD规范
  3. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  4. typeof define === 'function' && define.amd ? define(factory) :
  5. (global = global || self, global.Vue = factory());
  6. }(this, function () { 'use strict';
  7. ···
  8. // Vue 构造函数
  9. function Vue (options) {
  10. // 保证了无法直接通过Vue()去调用,只能通过new的方式去创建实例
  11. if (!(this instanceof Vue)
  12. ) {
  13. warn('Vue is a constructor and should be called with the `new` keyword');
  14. }
  15. this._init(options);
  16. }
  17. return Vue
  18. })

1.1.3 定义原型属性方法

Vue之所以能适应基础的开发场景,除了经常提到的支持组件化开发,以及完善的响应式系统等外,还有重要的一点是它提供了丰富的api方法,不管是静态还是原型方法,它们都丰富到足以满足我们日常基础的开发需求。所以熟练阅读vue-api文档并精准使用api方法是迈向熟练开发的前提。接下来我们看看这些方法属性是在哪里定义的,注意,该小节会忽略大部分属性方法具体的实现,这些详细的细节会贯穿在后续系列的分析中

首先是原型上的属性方法,在构造函数的定义之后,有这样五个函数,他们分别针对不同场景定义了Vue原型上的属性和方法。

  1. // 定义Vue原型上的init方法(内部方法)
  2. initMixin(Vue);
  3. // 定义原型上跟数据相关的属性方法
  4. stateMixin(Vue);
  5. //定义原型上跟事件相关的属性方法
  6. eventsMixin(Vue);
  7. // 定义原型上跟生命周期相关的方法
  8. lifecycleMixin(Vue);
  9. // 定义渲染相关的函数
  10. renderMixin(Vue);

我们一个个看,首先initMixin定义了内部在实例化Vue时会执行的初始化代码,它是一个内部使用的方法。

  1. function initMixin (Vue) {
  2. Vue.prototype._init = function (options) {}
  3. }

stateMixin方法会定义跟数据相关的属性方法,例如代理数据的访问,我们可以在实例上通过this.$datathis.$props访问到data,props的值,并且也定义了使用频率较高的this.$set,this.$delte等方法。

  1. function stateMixin (Vue) {
  2. var dataDef = {};
  3. dataDef.get = function () { return this._data };
  4. var propsDef = {};
  5. propsDef.get = function () { return this._props };
  6. {
  7. dataDef.set = function () {
  8. warn(
  9. 'Avoid replacing instance root $data. ' +
  10. 'Use nested data properties instead.',
  11. this
  12. );
  13. };
  14. propsDef.set = function () {
  15. warn("$props is readonly.", this);
  16. };
  17. }
  18. // 代理了_data,_props的访问
  19. Object.defineProperty(Vue.prototype, '$data', dataDef);
  20. Object.defineProperty(Vue.prototype, '$props', propsDef);
  21. // $set, $del
  22. Vue.prototype.$set = set;
  23. Vue.prototype.$delete = del;
  24. // $watch
  25. Vue.prototype.$watch = function (expOrFn,cb,options) {};
  26. }

eventsMixin会对原型上的事件相关方法做定义,文档中提到的vm.$on,vm.$once,vm.$off,vm.$emit也就是在这里定义的。

  1. function eventsMixin(Vue) {
  2. // 自定义事件监听
  3. Vue.prototype.$on = function (event, fn) {};
  4. // 自定义事件监听,只触发一次
  5. Vue.prototype.$once = function (event, fn) {}
  6. // 自定义事件解绑
  7. Vue.prototype.$off = function (event, fn) {}
  8. // 自定义事件通知
  9. Vue.prototype.$emit = function (event, fn) {
  10. }

lifecycleMixin,renderMixin两个都可以算是对生命周期渲染方法的定义,例如$forceUpdate触发实例的强制刷新,$nextTick将回调延迟到下次 DOM 更新循环之后执行等。

  1. // 定义跟生命周期相关的方法
  2. function lifecycleMixin (Vue) {
  3. Vue.prototype._update = function (vnode, hydrating) {};
  4. Vue.prototype.$forceUpdate = function () {};
  5. Vue.prototype.$destroy = function () {}
  6. }
  7. // 定义原型上跟渲染相关的方法
  8. function renderMixin (Vue) {
  9. Vue.prototype.$nextTick = function (fn) {};
  10. // _render函数,后面会着重讲
  11. Vue.prototype._render = function () {};
  12. }

1.1.4 定义静态属性方法

除了原型方法外,Vue还提供了丰富的全局api方法,这些都是在initGlobalAPI中定义的。

  1. /* 初始化构造器的api */
  2. function initGlobalAPI (Vue) {
  3. // config
  4. var configDef = {};
  5. configDef.get = function () { return config; };
  6. {
  7. configDef.set = function () {
  8. warn(
  9. 'Do not replace the Vue.config object, set individual fields instead.'
  10. );
  11. };
  12. }
  13. // 通过Vue.config拿到配置信息
  14. Object.defineProperty(Vue, 'config', configDef);
  15. // 工具类不作为公共暴露的API使用
  16. Vue.util = {
  17. warn: warn,
  18. extend: extend,
  19. mergeOptions: mergeOptions,
  20. defineReactive: defineReactive###1
  21. };
  22. // Vue.set = Vue.prototype.$set
  23. Vue.set = set;
  24. // Vue.delete = Vue.prototype.$delete
  25. Vue.delete = del;
  26. // Vue.nextTick = Vue.prototype.$nextTick
  27. Vue.nextTick = nextTick;
  28. // 2.6 explicit observable API
  29. Vue.observable = function (obj) {
  30. observe(obj);
  31. return obj
  32. };
  33. // 构造函数的默认选项默认为components,directive,filter, _base
  34. Vue.options = Object.create(null);
  35. ASSET_TYPES.forEach(function (type) {
  36. Vue.options[type + 's'] = Object.create(null);
  37. });
  38. // options里的_base属性存储Vue构造器
  39. Vue.options._base = Vue;
  40. extend(Vue.options.components, builtInComponents);
  41. // Vue.use()
  42. initUse(Vue);
  43. // Vue.mixin()
  44. initMixin$1(Vue);
  45. // 定义extend扩展子类构造器的方法
  46. // Vue.extend()
  47. initExtend(Vue);
  48. // Vue.components, Vue.directive, Vue.filter
  49. initAssetRegisters(Vue);
  50. }

看着源码对静态方法的定义做一个汇总。

  1. 为源码里的config配置做一层代理,可以通过Vue.config拿到默认的配置,并且可以修改它的属性值,具体哪些可以配置修改,可以先参照官方文档。
  2. 定义内部使用的工具方法,例如警告提示,对象合并等。
  3. 定义set,delet,nextTick方法,本质上原型上也有这些方法的定义。
  4. Vue.components,Vue.directive,Vue.filter的定义,这些是默认的资源选项,后续会重点分析。
  5. 定义Vue.use()方法
  6. 定义Vue.mixin()方法
  7. 定义Vue.extend()方法

现在我相信你已经对引入Vue的阶段有了一个大致的认识,在源码分析的初期阶段,我们不需要死磕每个方法,思路的实现细节,只需要对大致的结构有基本的认识。有了这些基础,我们开始进入这个章节的主线。