• 1.1 Vue的引入
    • 1.1.1 基础使用
    • 1.1.2 Vue构造器
    • 1.1.3 定义原型属性方法
    • 1.1.4 定义静态属性方法

    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的阶段有了一个大致的认识,在源码分析的初期阶段,我们不需要死磕每个方法,思路的实现细节,只需要对大致的结构有基本的认识。有了这些基础,我们开始进入这个章节的主线。