You-Dont-Know-JS是github上一个拥有9000多枚星星的JS教学文档,评价很高,为了避免和其他翻译文一样,容易陷入不宜读的混乱,也试图避免原文中过多数的术语导致我露怯,我只提取提取我理解的一些点,通俗的讲出来。今天先从第一章作用域开始吧:
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),如下图所示:
错误
但是各位有没有想过一个问题,被赋值的引用(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也未定义,但是由于是复制操作,系统会为你在外层全局域里自动创建一个。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。