Javascript 声明提升
在分析声明提升之前,我认为有必要知道的两点:
一、引擎查询变量的两种方式
引擎查询变量的方式可以分为 LHS 和 RHS 两种方式,通过“L”和“R”是可以大致了解意思,分别是赋值操作的左侧和右侧。(不能光是理解为“=”的左右侧可不行,因为赋值操作的形式有多种。)
简单说下我对这两种查询方式的理解:LHS:赋值操作的目标是谁。(查询变量)RHS:谁是赋值操作的源头。(查询变量的值)
这样说可能有些难以理解,举个栗子:
function foo(a){
// 这里存在一个隐式变量分配,LHS 查询变量 a,并赋值 2.
// 隐式 a = 2;
// 左边 LHS 查询变量 b,查询作用域中是否存在 b 这个变量。
// 右边 RHS 查询变量 a 的值,将 a 赋值给 b。
var b = a;
// 返回 a,b 是 RHS 查询变量 a 的值和变量 b 的值并使用。
return a + b;
}
// 左边 LHS 查询变量 c,查询作用域中是否存在 c 这个变量。
// 右边 RHS 引用函数 foo,将 2 作为参数传进去。
var c = foo(2);
二、异常
关于异常要强调一点,必须在严格模式下。因为在非严格模式下,LHS 查询若是在最顶层的全局作用域上找不到查询的变量,则会创建一个该名称变量返还给引擎。
ReferenceError:同作用域判别失败相关。(比如:作用域中遍寻不到所需的变量)TypeError:作用域判别成功了,但是对结果的操作是非法或不合理的。(比如:试图对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的值中的属性)
举个栗子:
“strict”
function foo() {
console.log(a) //undefined
console.log(b) //ReferenceError
}
var a = 2;
声明提升
一、初步了解
编写 javascript 代码时,很多时候都会觉得代码会自上而下的执行。但是碰到声明提升,这种想法就会被打破。
举个栗子:
a = 2;
var a;
console.log(a);
运行结果为: 2
如果按照常理的自上而下执行,那么 a 执行的预期结果应当是 undefined,然而为什么会是 2?这就是声明提升的结果。
二、进一步了解
当初步了解声明提升的时候,碰上下面的代码:
console.log(a);
var a = 2;
运行结果为:undefined
初步了解声明提升之后,会自然而然的认为,声明就会被提升,然而声明的时候赋值,却得不到变量的值。
其实,上面代码的运行步骤可以分解为:
var a; // 声明提升
console.log(a); // 打印 a 的值
a = 2; // 对 a 进行赋值
原来,声明提升就是字面上的声明提升,其余的操作(如:赋值和其他逻辑)都还在原地踏步。
声明一个函数进行相应的操作,会得到函数声明提升的结果。由此可以发现:变量和函数的声明都会被提升在其他代码的前面执行。
三、逐步了解
通过几次试验可以逐步了解到,其实声明提升就是:变量和函数的声明会被提升在其他代码(当前作用域)的前面执行。
走到这里,有人就会想到,要是函数表达式,也会进行提升吗?
答案是:不会。而且,即使是具名函数表达式,在名称标识符赋值之前也是不能使用的。
举个栗子:
foo(); //TypeError
bar(); //ReferenceError
var foo = function bar(){};
代码分解为:
var foo; // 变量声明提升
foo(); //foo 对 undefined 值进行函数调用导致非法操作,故 TypeError
bar(); //bar 函数并没有声明,故 ReferenceError
foo = function bar(){}; // 对 foo 进行赋值
所以:函数表达式在名称标识符赋值之前是不能使用的。
注意:1、每个作用域都会进行提升操作。(所以函数内部形成的作用域也会有提升操作,提升 操作仅限当前的函数内部作用域)2、在函数和变量提升时,函数优先提升。3、一个普通块内部的函数声明通常会被提升到所在的作用域的顶部。
四、深入了解
在阅读《你不知道的 javascript》时,学习 let 的过程中,会发现有说明提到:使用 let 进行的声明不会在作用域中进行提升。声明的代码在被运行之前是,声明并不存在。
举个栗子:
console.log(a);
let a = 2;
运行结果是:ReferenceError: Cannot access ‘a’ before initialization. // 初始化前无法访问 ”a”
然后回到之前我运行的代码,将 let 换为 var,返回的结果是 undefined。
二者结合,再加上阅读我用了两个月的时间才理解 let 这篇文章,发现对 let 是否提升有了一个更新的认识。
作者把 js 变量分成三部分操作:创建(create)、初始化(initialize)和赋值(assign)。
上面的操作之所以会有不同的响应并不是说 let 没有创建,而是有一个初始化的过程并没有执行。而在初始化之前使用变量,就会形成一个暂时性死区。
经过 var 和 let 和 function 的测试可以总结到:
var 的创建和初始化被提升,赋值不会被提升。
let 的创建被提升,初始化和赋值不会被提升。
function 的创建、初始化和赋值均会被提升。