一. 编译原理
JavaScript事实上是一种编译语言,但它不是提前变异的,也不会像其他语言一样,编译结果在分布式系统中进行移植。
JavaScript引擎比想象要复杂的多。JavaScript在执行任何代码前都要进行编译,比如: var a =2; ,先编译这段代码,然后做好执行的准备,通常马上就会执行它。
1.1 分词 / 词法分析
1.2 解析 / 语法分析
1.3 代码生成
二. 理解作用域
当你看见var a = 2; 这段程序时,很可能认为这是一句声明。但我们的新朋友引擎却不这么看。事实上,引擎认为这里有两个完全不同的声明,一个由编译器在编译时处理,另个在引擎运行时处理。
变量的赋值要进行2个操作,首先编译器在当前作用中声明一个变量 (如果之前没有声明过) ,然后在运行时引擎在作用中查找该变量,如果能够到就对它进行赋值。。
2.1作用域的嵌套
引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。
LHS 和RHS查询是重要的。如果查找的操作是对变量进行赋值,那么使用LHS查询;若使用的是获取变量的值,则进行RHS查询。
如果RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。值得注意的ReferenceError 是非常重要的异常类型。相较之下,当引擎执行LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下。
ES5 中引入了“严格模式”。同正常模式,或者说宽松/ 懒惰模式相比,严格模式在行为上有很多不同。其中一个不同的行为是严格模式禁止自动或隐式地创建全局变量。因此,在严格模式中LHS 查询失败时,并不会创建并返回一个全局变量,引擎会抛出同RHS 查询失败时类似的ReferenceError 异常。
接下来,如果RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用null 或undefined 类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作TypeError。
ReferenceError 同作用域判别失败相关,而TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的。
2.2函数作用域:
var a = 2;
function foo() { // <-- 添加这一行
var a = 3;
console.log( a ); // 3
} // <-- 以及这一行
foo(); // <-- 以及这一行
console.log( a ); // 2
虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的问题。首先,必须声明一个具名函数foo(),意味着foo 这个名称本身“污染”了所在作用域(在这个例子中是全局作用域)。其次,必须显式地通过函数名(foo())调用这个函数才能运行其中的代码。如果函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行,
这将会更加理想。幸好,JavaScript 提供了能够同时解决这两个问题的方案、既闭包的使用。
var a = 2;
函数作用域和块作用域 | 27
(function foo(){ // <-- 添加这一行
var a = 3;
console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2
3.1匿名和具名
3.2块级作用域
为其声明的变量隐式地了所在的块作用域。
3.3提升
a = 2;
var a;
console.log( a );
你认为console.log(..) 声明会输出什么呢?
很多开发者会认为是undefined,因为var a 声明在a = 2 之后,他们自然而然地认为变量被重新赋值了,因此会被赋予默认值undefined。但是,真正的输出结果是2。
var a = 2;
鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片段也会有同样的行为而输出2。还有人可能会认为,由于变量a 在使用前没有先进行声明,因此会抛出ReferenceError 异常。输出来的会是undefined。
var a;
a = 2;
console.log( a );
其中第一部分是编译,而第二部分是执行。类似地,我们的第二个代码片段实际是按照以下流程处理的:
var a;
console.log( a );
a = 2;
function foo() {
console.log( a ); // undefined
var a = 2;
}
var a;
console.log( a ); // undefined
foo(); // 不是ReferenceError, 而是TypeError!
var foo = function bar() {
// ...
};
这段程序中的变量标识符foo() 被提升并分配给所在作用域(在这里是全局作用域),因此foo() 不会导致ReferenceError。但是foo 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo() 由于对undefined 值进行函数调用而导致非法操作,因此抛出TypeError 异常。
同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};
这个代码片段经过提升后,实际上会被理解为以下形式:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
}
a = 2;
}
foo();