1

You-Dont-Know-JS是github上一个拥有9000多枚星星的JS教学文档,评价很高,为了避免和其他翻译文一样,容易陷入不宜读的混乱,也试图避免原文中过多数的术语导致我露怯,我只提取提取我理解的一些点,通俗的讲出来。今天先从第一章作用域开始吧:

原文
我的翻译folk,希望有识之士能跟我共同完成

JS的编译

js和传统语言一样,也需要编译执行,编译的过程通常分三步:

  • 标记+词法分析 Tokenizing/Lexing:: 把程序语言切成一段一段的片段,称之为tokens。比如,程序中的 var a = 2;,会被切成如下tokens:var, a, =, 2, ;。

  • 解析 Parsing: 把token流转换成树,是嵌套元素形式,符合语法结构和规则,这个树被称之为"AST" (Abstract Syntax Tree).
    var a = 2;的树,开始于顶层节点 VariableDeclaration, 包含子节点 Identifier (值 a), 另外一个子节点 AssignmentExpression 包含另外子节点 NumericLiteral (值 2).

  • 代码生成 Code-Generation: 这个过程是把AST转换成可执行代码, 这部分各程序语言和个平台之间会有极大的不同。 所以,刨除细节,var a = 2;会被转换成机器语言,在内存中创建一个变量 a,而后把值储存其中。

编译器对赋值的操作

当编译器遇到var a,它会询问域是否存在变量a,如果存在编译器忽略这个声明,如果不存在编译器要求域声明一个新变量a给自己。

编译器而后为引擎产生可执行代码,处理a = 2, 引擎首先询问域是否有这个变量,如果没有执行其他操作。

两种引用

LHS(Left-hand Side)引用和 RHS(Right-hand Side)引用,对于编译器来说,LHS指变量用于赋值,RHS是指变量用于取值。

function foo(a) {
    console.log( a ); // 2
}

foo( 2 );

这里,foo(), 引用(变量)foo是RHS引用,因为它是用于取值的操作(将函数foo的值取出来),参数a被隐含赋值2,a = 2这里的引用a是LHS引用,因为它用于赋值。console.log(a)里面的a也是LHS引用,因为它用于获取a的值。

这里说明的是, 代码里的变量, 对于编译器来说有两种类型,用于获取它值的是一种,本身是用于被赋值的是另外一种。

嵌套域 (Nested Scope)

嵌套域很好理解,如下代码

function foo(a) {
    console.log( a + b );
}

var b = 2;

foo( 2 ); // 4

foo函数域中没有变量b,当引用b被调用的时候,程序会逐级往上查找,直到找到变量b,最顶层为全局变量 (global scope),如下图所示:

fig1.png

错误

但是各位有没有想过一个问题,被赋值的引用(LHS)如果本域内不存在会产生什么情况?当然,大多数人都经历过ReferenceError,引用错误。

当LHS引用(赋值引用)在域中找不到的时候,js引擎会直接抛出错误,而RHS引用(调用引用)如果在域中找不到的时候,js引擎会向上一级域中查找,如果依然没有,会直接在全局域中为你自动创建一个(非严格模式下, 严格模式下也会报ReferenceError错误)。

考虑从如下代码:

function foo(a) {
    console.log( a + b );
}

foo( 2 );

这里的变量b因为在函数域中未定义,所以会报错

function foo(a) {
    b = a;
}

foo( 2 );

这里的变量b也未定义,但是由于是复制操作,系统会为你在外层全局域里自动创建一个。


fishenal
3.4k 声望159 粉丝

千山鸟飞绝万径人踪灭