1.编译原理:
(1)编译器、作用域、引擎
- 编译器会忽略重复声明
- 编译原理(p7):
例如:
var a=2
,编译器会进行如下处理:
- 1.遇到
var a
,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a
。- 2.接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理
a=2
这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫做a
的变量。如果是,引擎就会使用这个变量;如果否,引擎会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
【如果到最外层的作用域还未找到,引擎会抛出ReferenceError
异常。】
注:
• 引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程。
• 编译器:引擎的好朋友之一,负责语法分析及代码生成等脏活累活。(编译:词法解析-语法解析-代码生成。对于 JavaScript 来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内。)
• 作用域:引擎的另一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
(2)LHS、RHS、ReferenceError、TypeError (p12)
-
LHS查询是试图找到变量的容器本身(赋值操作的左侧,如:
var a = 2
中的a
),从而对其进行赋值; -
RHS查询是取得它的源值。例如:
console.log(a)
,需要对a
和console.log
都进行RHS查询。 - ReferenceError:同作用域判别失败相关。(以下详细说明)
- TypeError:表示作用域判别成功了,但是对结果的操作是非法或不合理的。(以下详细说明)
- 如果
RHS查询
在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError
异常。- 如果
RHS查询
找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性,那么引擎会抛出TypeError
异常。- 【非严格模式】如果
LHS查询
在最顶层(全局作用域)中也找不到目标变量,全局作用域中就会创建一个具有该名称的变量(全局变量),并将其返回给引擎。- 【严格模式】如果
LHS查询
在最顶层(全局作用域)中也找不到目标变量,不会创建并返回一个全局变量,引擎会抛出ReferenceError
异常。
2.词法作用域
- 逐级嵌套的作用域(作用域气泡)
- 词法作用域:简单地说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。
- 欺骗词法作用域:
eval(..)
和with
函数,不过这两个方法无法被引擎优化,因此性能会降低,不推荐使用。
3.函数作用域和块作用域
- 函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
- 块作用域:
with
、try/catch的catch
、let
、const
4.提升
- 函数声明和变量声明都会被提升。
- 变量声明提升,例如:
var a = 1;
foo();
function foo() {
console.log( a ); // undefined
var a = 2;
}
相当于:
function foo() {
var a;
console.log( a ); // undefined
a = 2;
}
var a;
a = 1;
foo();
- 函数优先:函数声明和变量声明都会被提升,但函数会首先被提升,然后才是变量。
- 例如:
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
相当于:
function foo() {
console.log(1);
}
var foo; //重复声明,被忽略
foo(); // 1
foo = function() {
console.log(2);
}
5.作用域闭包
(1) for循环中let的妙用(p51)
for循环头部的let声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,即每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
例如:
for(let i=1;i<=5;i++) {
setTimeout(function() {
console.log(i)
},i*1000)
}
相当于:
for(var i=1;i<=5;i++) {
let j=i;
setTimeout(function() {
console.log(j)
},j*1000)
}
(2) 模块(p53)
模块模式需要具备两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
【一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块】
- 模块如下:
function CoolModule() {
var something = "cool";
var another = [1,2,3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join());
}
return {
doSomething: doSomething,
doAnother: doAnother
}
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1,2,3