Skip to content

Commit e16133a

Browse files
committed
chore(ci): blog sync
1 parent b087ae5 commit e16133a

File tree

2 files changed

+316
-0
lines changed

2 files changed

+316
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
---
2+
title: Vue源码——模版编译(三)
3+
date: 2023-12-23T12:32:15Z
4+
summary:
5+
tags: []
6+
---
7+
8+
## 前言
9+
在上篇文章中,我们介绍了`$mount`中通过一系列方式获取到`template`,调用 compileToFunctions 函数通过传入`template`和好多个参数,获取到了`rander`,接下来我们来分析一下内部的原理
10+
```
11+
const { render, staticRenderFns } = compileToFunctions(
12+
template,
13+
{
14+
outputSourceRange: process.env.NODE_ENV !== "production",
15+
shouldDecodeNewlines,
16+
shouldDecodeNewlinesForHref,
17+
delimiters: options.delimiters,
18+
comments: options.comments,
19+
},
20+
this);
21+
```
22+
23+
## 模板编译器
24+
上文使用的 compileToFunctions 函数是 createCompiler 的返回值,调用这个函数时传入了我们的配置 baseOptions,
25+
```
26+
@ src/platforms/web/compiler/index.js
27+
const { compile, compileToFunctions } = createCompiler(baseOptions);
28+
export { compile, compileToFunctions };
29+
```
30+
而 createCompiler 其实是 createCompilerCreator 的返回值
31+
```
32+
export const createCompiler = createCompilerCreator(function baseCompile(
33+
template: string,
34+
options: CompilerOptions
35+
): CompiledResult {
36+
const ast = parse(template.trim(), options);
37+
if (options.optimize !== false) {
38+
optimize(ast, options);
39+
}
40+
const code = generate(ast, options);
41+
return {
42+
ast,
43+
render: code.render,
44+
staticRenderFns: code.staticRenderFns,
45+
};
46+
});
47+
```
48+
调用 createCompilerCreator 传入了 baseCompile 函数,这个函数内部声明了一个`ast`常量接收`parse`函数返回值,多数同学应该听说过这个词,ast抽象树,巴拉巴拉。。。。。。。。。。。。。。。。。。。。
49+
50+
我们传入的 baseCompile 函数在 createCompilerCreator 函数中其实就是担任了一个模板编译器的角色,我们看看这个函数时如何使用我们传入的编译器函数的:
51+
```
52+
export function createCompilerCreator(baseCompile: Function): Function {
53+
return function createCompiler(baseOptions: CompilerOptions) { ... }
54+
```
55+
很明显的,甚至从名字都可以看出,createCompilerCreator 的作用就是创建一个 createCompiler,那么有人就会问了:不能直接创建 createCompiler 函数吗,为什么非要搞这么复杂。
56+
### 高阶函数
57+
这里的代码可以引出一个词叫做<u>高阶函数</u>
58+
- 什么是高阶函数?
59+
接收一个或多个函数输入,并返回另一个函数的函数,即高阶函数。
60+
- 高阶函数有什么用?
61+
1、通俗的讲:高阶函数可以让我们通过封装一个稍微有点复杂的函数来生成很多简单的函数,提高代码灵活度,降低耦合。
62+
2、闭包:闭包也是高阶函数中的一个特征,使用闭包可以防止变量污染全局变量。
63+
此处通过`baseCompile`——编译函数,`baseOptions`——编译配置,组装成一个新增编译函数返回,如下:
64+
65+
```
66+
function compile(
67+
template: string,
68+
options?: CompilerOptions
69+
): CompiledResult {
70+
// 将 finalOptions 的 __proto__ 指向 baseOptions 的 prototype
71+
const finalOptions = Object.create(baseOptions);
72+
73+
const errors = [];
74+
const tips = [];
75+
76+
let warn = (msg, range, tip) => {
77+
(tip ? tips : errors).push(msg);
78+
};
79+
80+
if (options) {
81+
if (
82+
process.env.NODE_ENV !== "production" &&
83+
options.outputSourceRange
84+
) {
85+
// 匹配模板开头的空格记录长度
86+
const leadingSpaceLength = template.match(/^\s*/)[0].length;
87+
88+
warn = (msg, range, tip) => {
89+
const data: WarningMessage = { msg };
90+
if (range) {
91+
if (range.start != null) {
92+
data.start = range.start + leadingSpaceLength;
93+
}
94+
if (range.end != null) {
95+
data.end = range.end + leadingSpaceLength;
96+
}
97+
}
98+
(tip ? tips : errors).push(data);
99+
};
100+
}
101+
// merge custom modules
102+
if (options.modules) {
103+
finalOptions.modules = (baseOptions.modules || []).concat(
104+
options.modules
105+
);
106+
}
107+
// merge custom directives
108+
if (options.directives) {
109+
finalOptions.directives = extend(
110+
Object.create(baseOptions.directives || null),
111+
options.directives
112+
);
113+
}
114+
115+
// 拷贝其他的options
116+
for (const key in options) {
117+
if (key !== "modules" && key !== "directives") {
118+
finalOptions[key] = options[key];
119+
}
120+
}
121+
}
122+
123+
finalOptions.warn = warn;
124+
// 调用传入的baseCompile对模板进行解析
125+
const compiled = baseCompile(template.trim(), finalOptions);
126+
if (process.env.NODE_ENV !== "production") {
127+
detectErrors(compiled.ast, warn);
128+
}
129+
compiled.errors = errors;
130+
compiled.tips = tips;
131+
return compiled;
132+
}
133+
134+
return {
135+
compile,
136+
compileToFunctions: createCompileToFunctionFn(compile),
137+
};
138+
};
139+
```
140+
141+
随后,返回`compile`和我们之前使用的`compileToFunctions`,值得一提我们在返回的时候又调用了一个高阶函数`createCompileToFunctionFn`对此处的`compile`做包装,如下:
142+
```
143+
export function createCompileToFunctionFn(compile: Function): Function {
144+
// 缓存对象
145+
const cache = Object.create(null);
146+
// 将compile处理成函数的函数
147+
return function compileToFunctions(
148+
template: string,
149+
options?: CompilerOptions,
150+
vm?: Component
151+
): CompiledFunctionResult {
152+
// 首先将 options 拷贝一份到新的对象上
153+
options = extend({}, options);
154+
// 定义警告函数
155+
const warn = options.warn || baseWarn;
156+
delete options.warn;
157+
158+
// csp警告
159+
if (process.env.NODE_ENV !== "production") {
160+
// detect possible CSP restriction
161+
try {
162+
new Function("return 1");
163+
} catch (e) {
164+
if (e.toString().match(/unsafe-eval|CSP/)) {
165+
warn(
166+
"It seems you are using the standalone build of Vue.js in an " +
167+
"environment with Content Security Policy that prohibits unsafe-eval. " +
168+
"The template compiler cannot work in this environment. Consider " +
169+
"relaxing the policy to allow unsafe-eval or pre-compiling your " +
170+
"templates into render functions."
171+
);
172+
}
173+
}
174+
}
175+
176+
// 如果有自定义的delimiters(插值符号,默认为"{{}}")
177+
// 将符号拼接到前面,没有就不拼接
178+
const key = options.delimiters
179+
? String(options.delimiters) + template
180+
: template;
181+
// 进行缓存
182+
if (cache[key]) {
183+
return cache[key];
184+
}
185+
186+
// 编译结果
187+
const compiled = compile(template, options);
188+
189+
// 错误和提示处理
190+
if (process.env.NODE_ENV !== "production") {
191+
if (compiled.errors && compiled.errors.length) {
192+
if (options.outputSourceRange) {
193+
compiled.errors.forEach((e) => {
194+
warn(
195+
`Error compiling template:\n\n${e.msg}\n\n` +
196+
generateCodeFrame(template, e.start, e.end),
197+
vm
198+
);
199+
});
200+
} else {
201+
warn(
202+
`Error compiling template:\n\n${template}\n\n` +
203+
compiled.errors.map((e) => `- ${e}`).join("\n") +
204+
"\n",
205+
vm
206+
);
207+
}
208+
}
209+
if (compiled.tips && compiled.tips.length) {
210+
if (options.outputSourceRange) {
211+
compiled.tips.forEach((e) => tip(e.msg, vm));
212+
} else {
213+
compiled.tips.forEach((msg) => tip(msg, vm));
214+
}
215+
}
216+
}
217+
```
218+
在上面我们已经获取到了`compile`函数的返回值,也就是编译结果:ast,render,staticRenderFns 在这里,我们进一步的对这些值做处理:
219+
```
220+
// 转为函数
221+
const res = {};
222+
const fnGenErrors = [];
223+
res.render = createFunction(compiled.render, fnGenErrors);
224+
res.staticRenderFns = compiled.staticRenderFns.map((code) => {
225+
return createFunction(code, fnGenErrors);
226+
});
227+
228+
// check function generation errors.
229+
// this should only happen if there is a bug in the compiler itself.
230+
// mostly for codegen development use
231+
/* istanbul ignore if */
232+
if (process.env.NODE_ENV !== "production") {
233+
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
234+
warn(
235+
`Failed to generate render function:\n\n` +
236+
fnGenErrors
237+
.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`)
238+
.join("\n"),
239+
vm
240+
);
241+
}
242+
}
243+
244+
return (cache[key] = res);
245+
};
246+
}
247+
```
248+
rander表达式
249+
250+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: Vue源码——如何创建一个干净的map
3+
date: 2023-12-23T12:31:21Z
4+
summary:
5+
tags: []
6+
---
7+
8+
## 前言
9+
最近在阅读学习 Vue 源码,里面值得学习的地方很多,需要细品,我在读了一阵子源码的时候,偶然注意到了 Vue 创建对象的方式(在之前阅读的时候被我选择性的忽略掉了o(╥﹏╥)o),为了弥补我的过错,现在大晚上我直接惩罚自己赶紧写个博客推敲推敲。
10+
## Object.create()
11+
`Object.create`方法接收两个参数:
12+
- proto: 新创建对象的原型对象。
13+
- propertiesObject(可选):
14+
如果该参数被指定且不为 undefined,则该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属 性)将为新创建的对象添加指定的属性值和对应的属性描述符。这些属性对应于 Object.defineProperties() 的第二个参数。
15+
16+
17+
包括但不限于 Vue,很多的框架或者库在创建一个空的对象时都使用了`Object.create(null)`,难道是最佳实践?
18+
首先,我们平时敲代码的时候创建一个对象一般之间将一个对象赋值:`let obj = {}`,那么这两种方式有什么区别呢,我们上代码:
19+
```
20+
let obj1 = Object.create(null);
21+
let obj2 = {};
22+
23+
console.log(obj1);
24+
console.log(obj2);
25+
```
26+
我们用这两种方式分别创建两个对象:obj1,obj2。
27+
![](http://cdn.liuji.site/illustration/1076/2022-11-19_23-06-26.jpg)
28+
我们可以用一个图表示它们现在的关系
29+
![](http://cdn.liuji.site/illustration/1076/2022-11-20_00-09-30.jpg)
30+
不同的地方是 obj1 没有原型链的指向,是一个**干净**的对象。我们知道在创建一个对象的时候会自动的从原型链上继承属性和方法,但如果继承的一些无用的东西,多少会对性能造成一点的影响,例如`for in`遍历会把原型链上的属性都遍历一遍。而框架是很注重性能的,所以使用干净的对象
31+
32+
我们看看 MDN 上的解释
33+
> 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]] 在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 Object.setPrototypeOf(...) 语句上的时间花费,而且可能会延伸到**任何代码,那些可以访问任何** [[Prototype]] 已被更改的对象的代码。
34+
由于此特性是语言的一部分,因此引擎开发人员仍需要高效地(理想地)实现该特性。在引擎开发人员解决此问题之前,如果你担心性能问题,则应该避免设置对象的 [[Prototype]]。相反,你应该使用 Object.create() 来创建带有你想要的 [[Prototype]] 的新对象。
35+
36+
## 手撸 Object.create()
37+
```
38+
Object.prototype.mycreate = function (proto, defineProperties) {
39+
// proto 参数需为null 或者 除基本类型包装对象以外的对象
40+
if (typeof proto !== "object" && typeof proto !== "function") {
41+
throw new TypeError(
42+
`Object prototype may only be an Object or null: ${proto}`
43+
);
44+
}
45+
46+
if (defineProperties === null) {
47+
throw new TypeError("Cannot convert undefined or null to object");
48+
}
49+
50+
let obj = {};
51+
Object.setPrototypeOf(obj, proto);
52+
53+
// 添加属性描述符
54+
if (defineProperties !== undefined) {
55+
Object.defineProperties(obj, defineProperties);
56+
}
57+
return obj;
58+
};
59+
60+
let newObj = Object.mycreate(null)
61+
```
62+
63+
64+
65+
66+

0 commit comments

Comments
 (0)