Skip to content

Commit 05dcb5f

Browse files
committed
chore(ci): blog sync
1 parent 239584b commit 05dcb5f

File tree

1 file changed

+262
-0
lines changed

1 file changed

+262
-0
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
title: Vue源码阅读——Vue内部的初始化流程
3+
date: 2023-12-23T12:18:35Z
4+
summary:
5+
tags: []
6+
---
7+
8+
## new 一个 Vue 实例
9+
```
10+
<!DOCTYPE html>
11+
<html lang="en">
12+
<head>
13+
<meta charset="UTF-8" />
14+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
15+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
16+
<title>Document</title>
17+
</head>
18+
<body>
19+
<div id="app"></div>
20+
<script src="../dist/vue.js"></script>
21+
<script>
22+
new Vue({ el: "#app", template: "<span>Hello World</span>" });
23+
</script>
24+
</body>
25+
</html>
26+
27+
```
28+
`dist/vue.js`引入了打包后的 vue,传入要挂载的 DOM 的 id,template参数,vue 就成功渲染出来了,但今天
29+
30+
## Vue 的版本
31+
接下来我们从外往里地往下看,Vue 使用的 rollup 打包,配置文件位于 script 文件夹的 config.js 中,这里面着各种版本的打包配置,不同的版本有着不同的功能,runtime 表示包含 Vue 运行时的版本,compiler 表示包含编译器的版本,编译器可以识别我们写的 template,如果不包含 compiler 就仅能处理 rander 函数,我找到当前使用的同时有 runtime 和 compiler 的版本,也就是`web-full-dev`
32+
```
33+
// @ scripts/config.js
34+
// Runtime+compiler development build (Browser)
35+
'web-full-dev': {
36+
// 入口路径
37+
entry: resolve('web/entry-runtime-with-compiler.js'),
38+
// 出口路径与文件名
39+
dest: resolve('dist/vue.js'),
40+
// 打包输出格式
41+
format: 'umd',
42+
// 环境
43+
env: 'development',
44+
alias: { he: './entity-decoder' },
45+
banner
46+
},
47+
```
48+
配置的 entry 没有直接使用路径,而是为了代码的简洁集中配置了别名
49+
```
50+
// @ scripts/alias.js
51+
module.exports = {
52+
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
53+
compiler: resolve('src/compiler'),
54+
core: resolve('src/core'),
55+
shared: resolve('src/shared'),
56+
web: resolve('src/platforms/web'),
57+
weex: resolve('src/platforms/weex'),
58+
server: resolve('src/server'),
59+
sfc: resolve('src/sfc')
60+
}
61+
```
62+
这里是我们当前版本(entry-runtime-with-compiler)的入口,此处就是做了编译(compiler)工作:
63+
```
64+
@ src/platforms/web/entry-runtime-with-compiler
65+
66+
在Vue原型上添加了 $mount 方法
67+
const mount = Vue.prototype.$mount
68+
69+
Vue.prototype.$mount = function (
70+
el?: string | Element,
71+
hydrating?: boolean
72+
): Component {
73+
...
74+
// 模板编译相关操作
75+
...
76+
}
77+
```
78+
79+
我们再往里看,此处是 运行时(runtime)模块的入口:
80+
```
81+
@ src/platforms/web/runtime/index
82+
...
83+
Vue.prototype.$mount = function (
84+
el?: string | Element,
85+
hydrating?: boolean
86+
): Component {
87+
// inBrowser 是 Vue 封装的一个工具函数
88+
// const inBrowser = typeof window !== 'undefined'
89+
// 用来判断当前环境是否为浏览器(根据需要安装devtools)
90+
el = el && inBrowser ? query(el) : undefined;
91+
// 进入挂载阶段
92+
return mountComponent(this, el, hydrating);
93+
};
94+
...
95+
```
96+
如果使用的版本是 runtime 版本,是没有 compoiler 模块,也就是无法对 template 进行编译的,所以我们需要根据实际需求选择版本。
97+
98+
## Vue 的初始化
99+
接下来就是 Vue 的核心代码了
100+
```
101+
initGlobalAPI(Vue)
102+
103+
Object.defineProperty(Vue.prototype, '$isServer', {
104+
get: isServerRendering
105+
})
106+
107+
Object.defineProperty(Vue.prototype, '$ssrContext', {
108+
get () {
109+
/* istanbul ignore next */
110+
return this.$vnode && this.$vnode.ssrContext
111+
}
112+
})
113+
114+
// expose FunctionalRenderContext for ssr runtime helper installation
115+
Object.defineProperty(Vue, 'FunctionalRenderContext', {
116+
value: FunctionalRenderContext
117+
})
118+
119+
Vue.version = '__VERSION__'
120+
```
121+
122+
这里调用了 initGlobalAPI 函数,并传入了 Vue 的构造函数
123+
```
124+
// @src/core/global-api/index
125+
export function initGlobalAPI(Vue: GlobalAPI) {
126+
const configDef = {}
127+
configDef.get = () => config
128+
if (process.env.NODE_ENV !== 'production') {
129+
configDef.set = () => {
130+
warn(
131+
'Do not replace the Vue.config object, set individual fields instead.'
132+
)
133+
}
134+
}
135+
Object.defineProperty(Vue, 'config', configDef)
136+
```
137+
我们知道,传入的参数是 Vue 的构造函数,此处使用 Object.defineProperty 方法在Vue构造函数上新增了 config 属性,并定义该属性的<a target="_blank"
138+
href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty">存取描述符</a>,仅允许获取,赋值时进行警告。
139+
140+
然后挂载一系列的工具方法,这些相信我们大多都用过,我们在后面再做详细的了解:
141+
```
142+
Vue.util = {
143+
warn,
144+
extend,
145+
mergeOptions,
146+
defineReactive,
147+
};
148+
Vue.set = set;
149+
Vue.delete = del;
150+
Vue.nextTick = nextTick;
151+
// 2.6 explicit observable API
152+
Vue.observable = <T>(obj: T): T => {
153+
observe(obj);
154+
return obj;
155+
};
156+
```
157+
紧接着,在Vue上创建了一个空对象options:
158+
```
159+
Vue.options = Object.create(null);
160+
// 变量常量数组 ASSET_TYPES = ['component','directive','filter'],在 Vue.option 中创建空的对象
161+
ASSET_TYPES.forEach((type) => {
162+
Vue.options[type + "s"] = Object.create(null);
163+
});
164+
// 将 Vue.options._base 属性指向自身,此属性在下文中被用来判断是否为根实例
165+
Vue.options._base = Vue;
166+
// 这是shared中封装的一个工具函数,比较简单,
167+
// 两参数都是对象,作用是将参数二中的属性插入到参数一中
168+
// 此处将 Keep-alive 中缓存的数据合并到了 option
169+
extend(Vue.options.components, builtInComponents);
170+
171+
initUse(Vue);
172+
initMixin(Vue);
173+
initExtend(Vue);
174+
initAssetRegisters(Vue);
175+
```
176+
此处调用了四个 init 开头的函数,我们先大致的做一下了解,这四个函数做的操作就是在 Vue 上添加相应的方法(use,mixin,extend),initAssetRegisters 内部通过数组的变量添加了三个方法。
177+
## Vue 的构造函数
178+
此处就是 Vue 开始的地方
179+
```
180+
@ src/core/instance/index.js
181+
import { initMixin } from "./init";
182+
import { stateMixin } from "./state";
183+
import { renderMixin } from "./render";
184+
import { eventsMixin } from "./events";
185+
import { lifecycleMixin } from "./lifecycle";
186+
import { warn } from "../util/index";
187+
188+
function Vue(options) {
189+
//判断是否以new关键字创建的vue实例,否则抛出警告
190+
if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
191+
warn("Vue is a constructor and should be called with the `new` keyword");
192+
}
193+
// 调用_init
194+
this._init(options);
195+
}
196+
197+
// 挂载_init方法
198+
initMixin(Vue);
199+
stateMixin(Vue);
200+
eventsMixin(Vue);
201+
lifecycleMixin(Vue);
202+
renderMixin(Vue);
203+
204+
export default Vue;
205+
```
206+
那么问题来了,这个_init是哪来的?在下面调用的initMixin函数中,此处为Vue构造函数挂载了_init方法:
207+
```
208+
Vue.prototype._init = function (options?: Object) {
209+
const vm: Component = this;
210+
// uid是每个 Vue 实例的唯一标识
211+
vm._uid = uid++;
212+
// 一个避免被观察到的标记
213+
vm._isVue = true;
214+
// 对 option 进行合并操作,将相关的属性和方法合并到 vm.$options 对象之上
215+
if (options && options._isComponent) {
216+
// _isComponent 是Vue在创建组件流程中声明的属性
217+
// 如果是子组件初始化时走这里,这里只做了一些性能优化
218+
initInternalComponent(vm, options);
219+
} else {
220+
// 将用户传入配置合并到vm
221+
vm.$options = mergeOptions(
222+
// 合并 mixin 以及 extend 操作下影响的 option
223+
resolveConstructorOptions(vm.constructor),
224+
options || {},
225+
vm
226+
);
227+
}
228+
229+
vm._self = vm;
230+
// 初始化组件实例关系属性
231+
initLifecycle(vm);
232+
// 初始化自定义事件
233+
initEvents(vm);
234+
// 初始化 rander 和插槽
235+
initRender(vm);
236+
// 执行生命周期钩子beforeCreate
237+
callHook(vm, "beforeCreate");
238+
// 注入实例化
239+
initInjections(vm);
240+
// 数据响应式的实例化
241+
initState(vm);
242+
// 解析provide
243+
initProvide(vm); // resolve provide after data/props
244+
// 执行生命周期钩子created
245+
callHook(vm, "created");
246+
247+
//最后,判断是否是否传入`el`,如果有就调用$mount进入模板编译阶段
248+
249+
if (vm.$options.el) {
250+
vm.$mount(vm.$options.el);
251+
}
252+
253+
```
254+
255+
## 总结
256+
经过上面的分析,可以归纳为一下几个流程
257+
![](http://cdn.liuji.site/illustration/1036/2022-11-14_22-38-27.jpg)
258+
1. 初始化 Vue 构造函数上的方法
259+
2. 实例化对象 vm
260+
3. 调用 $mount 方法进入模板解析阶段
261+
262+

0 commit comments

Comments
 (0)