搜索
写经验 领红包
 > 电器

js变量提升和函数提升(js变量提升面试题)

导语:js变量提升

js变量提升和函数提升(js变量提升面试题)

直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全 正确,有一种特殊情况会导致这个假设是错误的。

考虑以下代码:

a = 2;

var a;

console.log( a );

你认为 console.log(..) 声明会输出什么呢?

很多开发者会认为是 undefined,因为 var a 声明在 a = 2 之后,他们自然而然地认为变量 被重新赋值了,因此会被赋予默认值 undefined。但是,真正的输出结果是 2。

考虑另外一段代码:

console.log( a );

var a = 2;

鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片 段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明, 因此会抛出 ReferenceError 异常。

不幸的是两种猜测都是不对的。输出来的会是 undefined。

那么到底发生了什么?看起来我们面对的是一个先有鸡还是先有蛋的问题。到底是声明 (蛋)在前,还是赋值(鸡)在前?

编译器再度来袭

为了搞明白这个问题,回忆一下,引擎会 在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的 声明,并用合适的作用域将它们关联起来。

因此,正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先 被处理。

当你看到 var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个 声明:var a; 和 a = 2;。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在 原地等待执行阶段。

我们的第一个代码片段会以如下形式进行处理:

var a;

a = 2;

console.log( a );

其中第一部分是编译,而第二部分是执行。

类似地,我们的第二个代码片段实际是按照以下流程处理的:

var a;

console.log( a );

a = 2;

因此,打个比方,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。

换句话说,先有蛋(声明)后有鸡(赋值)

只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变 了代码执行的顺序,会造成非常严重的破坏。

foo();

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

foo 函数的声明(这个例子还包括实际函数的隐含值)被提升了,因此第一行中的调用可 以正常执行。

另外值得注意的是,每个作用域都会进行提升操作。尽管前面大部分的代码片段已经简化 了(因为它们只包含全局作用域),而我们正在讨论的 foo(..) 函数自身也会在内部对 var a 进行提升(显然并不是提升到了整个程序的最上方)。因此这段代码实际上会被理解为下 面的形式:

function foo() { var a;

console.log( a ); // undefined

a = 2; }

foo();

可以看到,函数声明会被提升,但是函数表达式却不会被提升。

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... // ... }

本文内容由小葵整理编辑!