《你不知道的Javascript》赋值、作用域,作用域提升

59 阅读4分钟

变量赋值

引擎:从头到尾负责整个Javascript程序的编译及执行过程
编译器:负责语法分析及代码生成。
作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

变量的赋值操作会执行两个动作:

  1. 编译: 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过)。
  2. 运行:然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)

  • 如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询;
  • 如果目的是获取变量的值,就会使用 RHS 查询。

赋值操作符会导致 LHS 查询。=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。

JavaScript 引擎首先会在代码执行前对其进行编译,在这个过程中,像 var a = 2 这样的声 明会被分解成两个独立的步骤:

  1. 首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
  2. 接下来,a = 2 会查询(LHS 查询)变量 a 并对其进行赋值。LHS 和 RHS 查询都会在当前执行作用域中开始,如果有需要(也就是说它们没有找到所需的标识符),就会向上级作用域继续查找目标标识符,这样每次上升一级作用域(一层楼),最后抵达全局作用域(顶层),无论找到或没找到都将停止。

不成功的 RHS 引用会导致抛出 ReferenceError 异常。不成功的 LHS 引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用 LHS 引用的目标作为标识符,或者抛出 ReferenceError 异常(严格模式下)。

function foo(a) {
  var b = a;
  return a + b;
}
var c = foo( 2 );

/* 1. 找出所有的 LHS 查询(这里有 3 处!)
		c = ..;、a = 2(隐式变量分配)、b = ..
   2. 找出所有的 RHS 查询(这里有 4 处!)
		foo(2..、= a;、a ..、.. b
*/

如何区分函数声明和函数表达式?

区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

包装函数(自执行函数)

包装函数相当于函数表达式,并且函数名只能在本函数内部使用。(避免了污染外部作用域)

(function foo(){ // <-- 添加这一行
  var a = 3; 
  console.log( a ); // 3 
  console.log(foo); //foo变量只能在foo函数内部使用
})(); // <-- 以及这一行

自执行函数的两种写法:

//1.执行括号在内部
(function(){
    console.log(123);
 }())
//2.执行括号在外部
 (function(){
    console.log(123);
 })()

块作用域

try/catch会创建块级作用域

try { 
	undefined(); // 执行一个非法操作来强制制造一个异常
} 
catch (err) { 
	console.log( err ); // 能够正常执行!
} 
console.log( err ); // ReferenceError: err not found

提升

概念:在编译阶段所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。

函数提升

function 会进行函数提升,函数表达式不会发生提升。 案例:下面案例中,foo变量提升,但是foo不是函数类型,所以调用报错。 具名的函数表达式中,函数名成只能在函数内部使用

foo(); // TypeError
bar(); // ReferenceError 
var foo = function bar() { 
 // ... 
  
};

提升优先级

函数提升 > 变量提升

案例:var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽 略了),因为函数声明会被提升到普通变量之前

foo(); // 1
var foo;
function foo() { 
 console.log( 1 ); 
}
foo = function() { 
 console.log( 2 ); 
};

变量提升不可以覆盖函数提升,但是函数提升可以覆盖函数提升

案例:下例中,输出3。因为var foo 无法覆盖输出为1的函数,但是输出为3的函数也会进行函数提升,可以覆盖上面的函数提升,最终foo函数为输出3的函数。

foo(); // 3
function foo() { 
 console.log( 1 ); 
}
var foo = function() { 
 console.log( 2 ); 
}; 
function foo() { 
 console.log( 3 );
}