什么是提升?
前面介绍了作用域相关的内容,这一节主要拓展一下提升相关的知识点。
那什么是提升呢?
包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
也就是说,无论如何变量和函数的声明都会优先提到前面去执行,其他代码逻辑都轮到后面再说。
就比如:
var a = 2;
这一行简单的代码会被编译器拆解为步骤。
// 变量声明,提到前面去了
var a;
// 赋值操作,留在原地等待执行
a = 2;
说白了,提升其实就是一个声明移动到当前作用域最前面的过程。
变量提升
只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。
为了确保深入理解,我先举两个变量提升的例子来说明。
No.1
a = 2;
var a;
console.log(a);
这可不会报错啊,因为它实际上的执行顺序是这样的:
var a; // 声明会提前
a = 2;
console.log(a); // 2
No.2
console.log(a); // undefined
var a = 2;
实际上它的执行顺序:
var a; // 声明提前了
console.log(a); // undefined
a = 2;
但是提升不仅仅是针对变量的,很多人对提升的印象可能都只是“变量提升”,但实际上函数也会有提升的现象。
接下来我会慢慢详细介绍。
函数提升
函数提升可以类比变量提升,过程是一样的。
比如:
fn(); //先调用
function fn() { //再声明
console.log(1);
}
我们可以看到控制台是能正常打印结果的:
因为上面这段代码实际上是这样的执行的:
function fn() { // 函数声明提升
console.log(1);
}
fn();
有些会扣细节的人可能有点不明白为啥console.log(1)
也会一起打印出来,这里需要对“函数声明本身”理解透彻。
函数声明本身包括:函数的名称和函数体(也就是{}中的内容)。
所以 1 能被正确打印出来。这是被包含在函数体里的内容。
当我把 case 改造成这样:
fn();
function fn() { // 函数声明提升
console.log(a);
var a = 1;
}
汗流浃背了吗?再仔细 think think。
这里会打印的是 undefined。
有两次的提升,一次是在全局作用域下的 fn 函数声明提升,一次是在 fn 函数作用域中的 a 出现的变量声明提升。
这个 case 给我们一个启发:
每个作用域都会进行提升操作。
但是呢,还有有一个问题,假如当变量声明和函数声明同时存在的时候,会出现什么情况呢?
提升的过程是否存在优先级呢?
答案是肯定的。
函数被提升的优先级大于变量。
比如:
foo();
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
输出会是怎样?
首先,由于函数提升>变量提升,那么function foo() { console.log(1); }
会先被提到全局作用域的顶层。
然后是变量提升,var foo;
。
看完提升之后,我们开始从上往下分析代码的执行:
-
foo();
-->foo指向第一个函数,输出1。 -
var foo;
实际上这一行会被引擎忽略。 -
foo = function() { console.log(2) }
此时foo指向一个新函数,打印出2。
啊对,这里我需要补充一下。
这玩意儿很重要。
如果是函数表达式呢。如下:
foo();
var foo = function() {
console.log(1);
}
这种情况下还会有变量提升吗?
答案是不会。
函数声明会被提升,但是函数表达式不会。
可见,控制台抛出了一个 TypeError 的异常。
⚠️⚠️但这里抛出的是 TypeError 异常诶!
按理来说,没有被提升的情况下,尝试使用一个未被声明(即不存在于作用域中)的变量会导致抛出的异常应该是 ReferenceError。
实际上,这段代码里面的foo()
这个变量标识符是有被提升的,并且被分配给全局作用域。
就是,foo作为一个变量被提升。var foo = ...
。
所以不会导致 ReferenceError。
但因为此时 foo 没有被赋予任何值,还是 undefined。
此时的foo()
=== undefined()
。
对 undefined 值进行函数调用而导致的非法操作,才是 TypeError。
小结
今天主要介绍了提升的内容,深入理解提升的过程,包含函数提升和变量提升。
其中函数提升的优先级会大于变量提升。
也需要注意,每一个作用域都会有自己的提升操作。
好啦,今天的任务Done~ 明天再见!