Skip to content

Commit 573247d

Browse files
committed
chore: add v8
1 parent 5ee015e commit 573247d

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

blog/JavaScript核心原理解析.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,189 @@ while (node = node.next) {
162162
```
163163

164164
## export default function() {}:你无法导出一个匿名函数表达式
165+
166+
- export ...语句通常是按它的词法声明来创建的标识符的,例如export var x = ...就意味着在当前模块环境中创建的是一个变量,并可以修改等等。但是当它被导入时,在import语句所在的模块中却是一个常量,因此总是不可写的。
167+
- 由于export default ...没有显式地约定名字“default(或default)”应该按let/const/var的哪一种来创建,因此 JavaScript 缺省将它创建成一个普通的变量(var),但即使是在当前模块环境中,它事实上也是不可写的,因为你无法访问一个命名为“default”的变量——它不是一个合法的标识符。
168+
- 所谓匿名函数,仅仅是当它直接作为操作数(而不是具有上述“匿名函数定义”的语法结构)时,才是真正匿名的,例如:
169+
170+
```js
171+
console.log((function(){}).name)); // ""
172+
```
173+
174+
- 由于类表达式(包括匿名类表达式)在本质上就是函数,因此它作为 default 导出时的性质与上面所讨论的是一致的。
175+
- 导出项(的名字)总是作为词法声明被声明在当前模块作用域中的,这意味着它不可删除,且不可重复导出。亦即是说即使是用var x...来声明,这个x也是在 _lexicalNames_ 中,而不是在 _varNames_ 中。
176+
- 所谓“某个名字表”,对于 export 来说是模块的导出表,对于 import 来说就是名字空间(名字空间是用户代码可以操作的组件,它映射自内部的模块导入名字表)。不过,如果用户代码不使用“import * as …”的语法来创建这个名字空间,那么该名字表就只存在于 JavaScript 的词法分析过程中,而不会(或并不必要)创建它在运行期的实例。这也是我一直用“某个名字表”来称呼它的原因,它并不总是以实体形式存在的。
177+
- 没有模块会导出(传统意义上的)main(),因为 ECMAScript 为了维护模块的静态语义,而把执行过程及其入口的定义丢回给了引擎或宿主本身。
178+
179+
**Q&A**
180+
181+
- `export default function(){}`。这个语法本身没有任何的问题。但是他看似导出一个匿名函数表达式。其实他真正导出的是一个具有名字的函数,名字的default。
182+
- ESModule 根据 import 构建依赖树,所以在代码运行前名字就是已经存在于上下文,然后在运行模块最顶层代码,给名字绑定值,就出现了‘变量提升’的效果。
183+
184+
## for (let x of [1,2,3]) ...:for循环并不比使用函数递归节省开销
185+
186+
绝大多数 JavaScript 语句都并没有自己的块级作用域。从语言设计的原则上来看,越少作用域的执行环境调度效率也就越高,执行时的性能也就越好。
187+
188+
基于这个原则,switch语句被设计为有且仅有一个作用域,无论它有多少个 case 语句,其实都是运行在一个块级作用域环境中的。例如:
189+
190+
```js
191+
var x = 100, c = 'a';
192+
switch (c) {
193+
case 'a':
194+
console.log(x); // ReferenceError
195+
break;
196+
case 'b':
197+
let x = 200;
198+
break;
199+
}
200+
```
201+
202+
在这个例子中,switch 语句内是无法访问到外部变量x的,即便声明变量x的分支case 'b'永远都执行不到。这是因为所有分支都处在同一个块级作用域中,所以任意分支的声明都会给该作用域添加这个标识符,从而覆盖了全局的变量x。
203+
204+
一些简单的、显而易见的块级作用域包括:
205+
206+
```js
207+
// 例 1
208+
try {
209+
// 作用域 1
210+
}
211+
catch (e) { // 表达式 e 位于作用域 2
212+
// 作用域 2
213+
}
214+
finally {
215+
// 作用域 3
216+
}
217+
// 例 2
218+
//(注:没有使用大括号)
219+
with (x) /* 作用域 1 */; // <- 这里存在一个块级作用域
220+
// 例 3, 块语句
221+
{
222+
// 作用域 1
223+
// 除了这三个语句和“一个特例”之外,所有其它的语句都是没有块级作用域的。例如`if`条件语句的几种常见书写形式:
224+
if (x) {
225+
...
226+
}
227+
// or
228+
if (x) {
229+
...
230+
} else {
231+
...
232+
}
233+
```
234+
235+
这些语法中的“块级作用域”都是一对大括号表示的“块语句”自带的,与上面的“例 3”是一样的,而与if语句本身无关。
236+
237+
**循环语句中的块**
238+
239+
并不是所有的循环语句都有自己的块级作用域,例如 `while``dowhile` 语句就没有。而且,也不是所有 for 语句都有块级作用域。在 JavaScript 中,有且仅有:
240+
241+
```js
242+
for (<let/const>…) …
243+
```
244+
245+
这个语法有自己的块级作用域。当然,这也包括相同设计的`for await``for .. of/in ..`。例如:
246+
247+
```js
248+
for await (<let/const>x of …) …
249+
for (<let/const>xin …) …
250+
for (<let/const>xof …) …
251+
```
252+
253+
语句`for (<const/let> x ...) ...`语法中的标识符x是一个词法名字,应该由for语句为它创建一个(块级的)词法作用域来管理之。
254+
255+
**第二个作用域**
256+
257+
```js
258+
var x = 100;
259+
for (let x = 102; x < 105; x++)
260+
console.log('value:', x); // 显示“value: 102~104”
261+
console.log('outer:', x); // 显示“outer: 100”
262+
```
263+
264+
因为for语句的这个块级作用域的存在,导致循环体内访问了一个局部的x值(循环变量),而外部的(outer)变量x是不受影响的。
265+
266+
那么在循环体内是否需要一个新的块级作用域呢?这取决于在语言设计上是否支持如下代码:
267+
268+
```js
269+
for (let x = 102; x < 105; x++)
270+
let x = 200;
271+
```
272+
273+
也就是说,如果循环体(单个语句)允许支持新的变量声明,那么为了避免它影响到循环变量,就必须为它再提供另一个块级作用域。很有趣的是,在这里,JavaScript 是不允许声明新的变量的。上述的示例会抛出一个异常,提示“单语句不支持词法声明”:
274+
275+
```bash
276+
SyntaxError: Lexical declaration cannot appear in a single-statement context
277+
```
278+
279+
**for 循环的代价**
280+
281+
```js
282+
for (let i in x)
283+
setTimeout(()=>console.log(i), 1000);
284+
```
285+
286+
这个例子创建了一些定时器。当定时器被触发时,函数会通过它的闭包(这些闭包处于 loopEnv 的子级环境中)来回溯,并试图再次找到那个标识符i。然而,当定时器触发时,整个 for 迭代有可能都已经结束了。这种情况下,要么上面的 forEnv 已经没有了、被销毁了,要么它即使存在,那个i的值也已经变成了最后一次迭代的终值。
287+
288+
所以,要想使上面的代码符合预期,这个 loopEnv 就必须是“随每次迭代变化的”。也就是说,需要为每次迭代都创建一个新的作用域副本,这称为迭代环境(iterationEnv)。因此,每次迭代在实际上都并不是运行在 loopEnv 中,而是运行在该次迭代自有的 iterationEnv 中。
289+
290+
也就是说,在语法上这里只需要两个“块级作用域”,而实际运行时却需要为其中的第二个块级作用域创建无数个副本。
291+
292+
这就是 for 语句中使用“let/const”这种块级作用域声明所需要付出的代价。
293+
294+
**总结**
295+
296+
- 当在这样的 for 循环中添加块语句时,块语句在每个迭代中都会都会创建一次它自己的**块级作用域副本**。这个循环体越大,支持的层次越多,那么这个环境的创建也就越频繁,代价越高昂。
297+
- 无论用户代码是否直接引用 loopEnv 中的循环变量,这个过程都是会发生的。这是因为 JavaScript 允许动态的 eval(),所以引擎并不能依据代码文本静态地分析出循环体(ForBody)中是否引用哪些循环变量。
298+
- **“循环与函数递归在语义上等价”**。所以在事实上,上述这种 for 循环并不比使用函数递归节省开销。在函数调用中,这里的循环变量通常都是通过函数参数传递来处理的。因而,那些支持“let/const”的 for 语句,本质上也就与“在函数参数界面中传递循环控制变量的递归过程”完全等价,并且在开销上也是完全一样的。
299+
- 因为每一次函数调用其实都会创建一个新的闭包——也就是函数的作用域的一个副本。
300+
301+
**Q&A**
302+
303+
- for只要写大括号就代表着块级作用域。所以只要写大括号,不管用let 还是 var,一定是会创建相应循环数量的块级作用域的。
304+
- 如果不用大括号,在for中使用了let,也会创建相应循环数量的块级作用域。也就是说,可以提高性能的唯一情况只有(符合业务逻辑的情况下),循环体是单行语句就不使用大括号且for中使用var
305+
- 因为单语句没有块级作用域,而词法声明是不可覆盖的,单语句后面的词法声明会存在潜在的冲突。
306+
307+
## x: break x; 搞懂如何在循环外使用break,方知语句执行真解
308+
309+
1.GOTO 语句是有害的”。1972 年图灵奖得主——迪杰斯特拉(Edsger Wybe Dijkstra, 1968
310+
2. 很多新的语句或语法被设计出来用来替代 GOTO 的效果的,但考虑到 GOTO 的失败以及无与伦比的破坏性,这些新语法都被设计为功能受限的了。
311+
3. 任何的一种 GOTO 带来的都是对“顺序执行”过程的中断以及现场的破坏,所以也都存在相应的执行现场回收的机制。
312+
4. 有两种中断语句,它们的语义和应用场景都不相同。
313+
5. 语句有返回值。
314+
6. 在顺序执行时,当语句返回 Empty 的时候,不会改写既有的其他语句的返回值。
315+
7. 标题中的代码,是一个“最小化的 break 语句示例”。
316+
317+
## `${1}`:详解JavaScript中特殊的可执行结构
318+
319+
模板字面量本身是一个特殊的可执行结构,但是它调动了包括引用、求值、标识符绑定、内部可执行结构存储,以及执行函数调用在内的全部能力。这是 JavaScript 厘清了所有基础的可执行结构之后,才在语法层面将它们融汇如一的结果。
320+
321+
**总结**
322+
323+
1. 标题中的代码称为模板字面量,是一种可执行结构。JavaScript 中有许多类似的可执行结构,它们通常要用固定的逻辑、在确定的场景下交付 JavaScript 的一些核心语法的能力。
324+
2. 与参数表和赋值模板有相似的地方,模板字面量也是将它的形式规格(Formal)作为可执行结构来保存的。
325+
3. 只是参数表与赋值模板关注的是名字,因此存储的是“名字(lhs)”与“名字的值(rhs)的取值方法”之间的关系,执行的结果是 argArray 或在当前作用域中绑定的名字等。
326+
4. 而模板字面量关注的是值,它存储的是“结果”与“结果的计算过程”之间的关系。由于模板字面量的执行结果是一个字符串,所以当它作为值来读取时,就会激活它的运算求值过程,并返回一个字符串值。
327+
5. 模板字面量与所有其它字面量(能作为引用)相似,它也可以作为引用。
328+
1. ```js
329+
1=1
330+
```
331+
2.1=1”包括了“1”作为引用和值(lhs 和 rhs)的两种形式,在语法上是成立的。
332+
3. ```
333+
foo`${1}`
334+
```
335+
4. 所以上面这行代码在语法上也是成立的。因为在这个表达式中,${1}使用的不是模板字面量的值,而是它的一个“(类似于引用的)结构”。
336+
6. “模板字面量调用(TemplateLiteral Call)”是唯一一个会使用模板字面量的引用形态(并且也没有直接引用它的内部结构)的操作。这种引用形态的模板字面量也被称为“标签模板(Tagged Templates)”,主要包括模板的位置和那些可计算的标签的信息。例如:
337+
1. ```
338+
> var x = 1;
339+
> foo = (...args) => console.log(...args);
340+
> foo`${x}`
341+
[ '', '' ] 1
342+
```
343+
2. 模板字面量的内部结构中,主要包括将模板多段截开的一个数组,原始的模板文本(raw)等等。在引擎处理模板时,只会将该模板解析一次,并将这些信息作为一个可执行结构缓存起来(以避免多次解析降低性能),此后将只使用该缓存的一个引用。当它作为字面量被取值时,JavaScript 会在当前上下文中计算各个分段中的表达式,并将表达式的结果值填回到模板从而拼接成一个结果值,最后返回给用户。
344+
345+
## x => x:函数式语言的核心抽象:函数与表达式的同一性
346+
347+
1. 传入参数的过程执行于函数之外,例如`f(a=100);`绑定参数的过程执行于函数(的闭包)之内,例如`function foo(x=100) ..`
348+
2. `x=>x`在函数界面的两端都是值操作,也就是说 input/output 的都是数据的值,而不是引用。
349+
3. 参数有两种初始化方法,它们根本的区别在于绑定初值的方式不同。
350+
4. 闭包是函数在运行期的一个实例。

blog/从0开始学架构.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# 从0开始学架构
2+
3+
## 架构到底是指什么?
4+
5+
对于技术人员来说,“架构”是一个再常见不过的词了。但如果深究一下“架构”到底指什么,大部分人也许并不一定能够准确地回答。例如:
6+
7+
- 架构和框架是什么关系?有什么区别?
8+
- Linux 有架构,MySQL 有架构,JVM 也有架构,使用 Java 开发、MySQL 存储、跑在 Linux 上的业务系统也有架构,应该关注哪个架构呢?
9+
- 微信有架构,微信的登录系统也有架构,微信的支付系统也有架构,当我们谈微信架构时,到底是在谈什么架构?
10+
11+
**系统与子系统**
12+
13+
子系统的定义和系统定义是一样的,只是观察的角度有差异,一个系统可能是另外一个更大系统的子系统。按照这个定义,系统和子系统比较容易理解。我们以微信为例来做一个分析。
14+
15+
1. 微信本身是一个系统,包含聊天、登录、支付、朋友圈等子系统。
16+
2. 朋友圈这个系统又包括动态、评论、点赞等子系统。
17+
3. 评论这个系统可能又包括防刷子系统、审核子系统、发布子系统、存储子系统。
18+
4. 评论审核子系统不再包含业务意义上的子系统,而是包括各个模块或者组件,这些模块或者组件本身也是另外一个维度上的系统。例如,MySQL、Redis 等是存储系统,但不是业务子系统。
19+
20+
**模块与组件**
21+
22+
模块和组件都是系统的组成部分,只是从不同的角度拆分系统而已。
23+
24+
**逻辑的角度**来拆分系统后,得到的单元就是“模块”;从**物理的角度**来拆分系统后,得到的单元就是“组件”。划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。
25+
26+
> 架构是顶层设计;框架是面向编程或配置的半成品;组件是从技术维度上的复用;模块是从业务维度上职责的划分;系统是相互协同可运行的实体。

0 commit comments

Comments
 (0)