「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
一、背景
这一部分我考虑过很久,觉得还是要加上这一部分,这一部分是必修的,考虑到不是人人都像我这种摸鱼专家,有时间学习源码,直接就讲源码大家会蒙圈的,这里先按照js的一个执行顺序,先把这个代码的执行顺序捋一捋,这样方便后面理解,这部分属于磨刀不费砍柴工。
二、test.html 中的玄机
在前文,浅羲Vue源码-准备 中最后有一个 test.html 的文件,注意看它的 script 标签顺序:
<script src="./dist/vue.js"></script>
<script>
debugger
const sub = {
template: `
<div style="color: red;background: #5cb85c;display: inline-block">{{ someKey + foo }}</div>`,
props: {
someKey: {
type: String,
default: 'hhhhhhh'
}
},
inject: ['foo']
};
new Vue({
el: '#app',
data: {
msg: 'hello vue'
},
hahaha: 'hahahahahha',
provide: {
foo: 'bar'
},
components: {
someCom: sub
}
})
</script>
2.1 加载并执行 vue.js
这一点大家都清楚,浏览器解析 HTML
的时候,遇到 script
标签会等 script
加载 src
对应的资源,而这里 src
对应的是一个 js
文件,加载完还不要紧,要紧的是浏览器检测到这个资源是 js
的时候,就会执行它
,注意是执行
它,不执行他就是一段文本,只有执行了才是变量、函数...
接着我们看看 dist/vue.js
的大致结构:
(function (global, factory) {
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';
var emptyObject = Object.freeze({});
// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
function isUndef (v) {
return v === undefined || v === null
}
// ....
function Vue (options) {
if (
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
initMixin(Vue);
stateMixin(Vue);
// .....
Vue.compile = compileToFunctions;
return Vue;
})));
2.2 Vue 这个变量的注册
从上面的 vue.js
的代码结构可以看得出来,首先是个自执行函数(IIFE),这个自执行函数定义了两个形参:global
和 factory
,而后自执行和函数接收了两个实参,第一个是 this
,另一个是个函数,很显然 global
对应 this
,factory
对应这个匿名函数;
然后自执行函数中判断 exports
和 define
这两个对象,分别以 CMD
和 AMD
的方式注册 Vue
,当然这里是不有 exports
和 define
的,自然就是最后这一段:
(global = global || self, global.Vue = factory());
global.Vue = factory()
,那么 globa
l 是谁呢?global
是 前面的 this
啊,this
是谁,this
在全局作用域中就是 window
对象,factory
就是这个下面匿名函数,下称工厂函数:
(function () { 'use strict';
var emptyObject = Object.freeze({});
// ....
function Vue (options) {
if (
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
// ....
return Vue;
}))
所以 global.Vue = factory()
的执行后等效于 window.Vue = funciton Vue() {}
; 如此一来,就把 Vue
以 window
对象属性的形式注册到全局作用域了。
三、关于工厂函数中 initGlobalAPI() 细节
在工厂函数从上到下执行的时候,除了注册 Vue 这个类,还完成了一些 Vue 的全局配置,其中一部分是在 initGlobalAPI 这个方法中完成的。
这部分其实也是 Vue 的面向对象设计的妙处,这里不表架构设计,但是得先有个印象,这些全局的配置是要复用
的,我们所熟知的 Vue
的全局组件(keep-alive)
、全局指令
、全局过滤器
都是在这里初始化的。
而后面所谓的复用
,则是创建组件
时通过创建 Vue 的子类
,而创建子类时是要继承 Vue
的这部分全局配置,以实现全局
的能力,有没有人清楚这个设计模式呢,请评论区留意言吖~
// vue factory 函数片段
function initGlobalAPI (Vue) {
// details of initGlobalAPI
}
initGlobalAPI(Vue);
3.1 Vue.options
这里有一个 Vue.options 是在 initGlobalAPI 初始化的,这个 options 就是 Vue 全局 options,其中包含了全局的components、directives、filters,这个 Vue.options 在后面经常以 Ctor.options 的形式出现,这个先提一下,具体初始化过程如下:
// vue factory 函数片段
function initGlobalAPI (Vue) {
// ASSET_TYPES = [
// 'component',
// 'directive',
// 'filter'
// ];
// builtInComponents = {
// KeepAlive: KeepAlive
// };
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
extend(Vue.options.components, builtInComponents);
Vue.options._base = Vue;
}
initGlobalAPI(Vue);
经过上面过程,Vue.options
初始化完成应该是这样子的了:
Vue.options = {
components: {
keepAlive: keepAlive,
},
directives: {},
filters: {},
_base: Vue
}
_base
, 这个后面也常用到,Ctor.options._base
其实就是在说 Vue
了。
3.2 部分全局方法注册
// vue factory 函数片段
function initGlobalAPI (Vue) {
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
// 2.6 explicit observable API
Vue.observable = function (obj) {
observe(obj);
return obj
};
// Vue.options ....
initUse(Vue); // 注册 Vue.use 方法
initMixin$1(Vue); // 注册 Vue.mixin 方法
initExtend(Vue); // 注册 Vue.extend 方法
initAssetRegisters(Vue); // 注册 Vue.component / Vue.directive / Vue.filter 方法
}
initGlobalAPI(Vue);
经过上面的这一系列操作,有了 Vue.options
(下文中常以 Ctor.options 或 vm.constructor.options
出现,你发现我已经提醒好几次,说明这个真的很重要),有了 Vue.component(注册全局组件) / Vue.directive(注册全局指令) / Vue.filter(全局过滤器)
方法
四、总结
以下有需要注意的点,也有谨记的内容
4.1 你仍然没开始看源码
我们现在还在看 vue.js
这个经过 rollup
编译输出的产物,注意这个不是源码。为什么说这个,因为这个vue.js
只有一份,代码从上到下执行很符合预期,而源码是 ESModule
,各种 import / export
,很多内容不在一个模块,这就给源码阅读增加了不少麻烦。
我在这里,提前交代一些全局的内容,比如 Vue.options
马上就要用到了,后面 new Vue(options)
的时候,会各种 mergeOptions
,你会感到迷惑,为啥要 mergeOptions?谁又和谁 merge 了?因为你不知道 options是个什么东西。
另外,请谨记当我们的代码:new Vue()
执行的时候,vue.js
中的所有内容都已经执行过了,我们才会有 Vue
这个类。
4.2 梳理
test.html
中的vue.js
的script
标签要写在我们 demo 代码的前面,确保其先加载和执行;- 在我们的 demo 中,
Vue
通过window.Vue
的方式挂载在全局作用域; - 在工厂函数中有个
initGlobalAPI
会初始化Vue.options
和一些全局的方法;