【前端名狮】变量提升和函数提升

4,790 阅读4分钟

面试题目(腾讯):

下面题目输出结果是什么?

var a=2;
function a() {
    console.log(3);
}
console.log(typeof a);

答案解析:

这道题目主要考察JS的变量提升和函数提升相关的知识点

1. 变量提升

ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分。下面两个示例包含了变量提升的两种情况(全局作用域和函数作用域):

示例 1

console.log(a);
var a = 8;

// 变量提升后等价于下面代码
var a;
console.log(a) // undefined
a = 8;

示例 2

var a = 8;
function fn() {
    console.log(a);
    var a = 9;
    console.log(a);
}

// 变量提升后等价于下面代码

var a = 8;
function fn() {
    var a;
    console.log(a);
    a = 9;
    console.log(a);
}

2. 函数提升

js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升

示例

console.log(f1);  
console.log(f2);  
function f1() {}
var f2 = function() {}

// 函数提升后等价于下面代码

function f1() {}
var fn;
console.log(f1); // function f1() {}   
console.log(f2); // undefined  
f2 = function() {}

3. 变量提升 VS 函数提升

上面的面试题目就属于这种类型,变量和函数同名。当两种提升都存在的情况下,结果是什么样的呢?

var a=2;
function a() {
    console.log(3);
}
console.log(typeof a);

// 变量提升和函数提升后,代码变为

function a() {
    console.log(3);
}
a=2;
console.log(typeof a); // number

为什么是这样呢?这就需要从JS的编译过程解释了。

在JS代码执行前,会执行词法分析。所以JS运行要分为词法分析和执行两个阶段。

函数在运行的瞬间,会生成一个活动对象Active Object,简称AO

  1. 分析形参
    1. 如果函数有形参,则给当前活动对象增加属性,赋值为undefined
  2. 分析变量
    1. 如果AO上还没有 XXX 属性,则给当前活动对象增加属性,赋值为undefined.
    2. 如果AO上有 XXX 属性,则不做任何影响。
  3. 分析函数
    1. 把函数赋值给 AO.func 属性
    2. 如果此前 func 属性已存在,则覆盖。(证明函数的优先级比较高)

面试题目分析系过程大致如下:

  1. 分析形参
    • 因为是全局环境,可以理解为自执行函数,没有形参。
  2. 分析变量
    • 按照2.1的规则,AO.a = undefined
  3. 分析函数
    • 按照3.2的规则直接覆盖掉a属性,AO.a = function(){console.log(3)}

4. 为什么会有提升

Brendan Eich给出了答案:

  1. 由于第一代JS虚拟机中的抽象纰漏导致的,编译器将变量放到了栈槽内并编入索引,然后在(当前作用域的)入口处将变量名绑定到了栈槽内的变量。(注:这里提到的抽象是计算机术语,是对内部发生的更加复杂的事情的一种简化。)
  2. 函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。

总体意思就是:变量提升是人为实现的问题,而函数提升在当初设计时是有目的的。

补充:

js中无论哪种形式声明(var,let,const,function,function*,class)都会存在提升现象,不同的是, var,function,function*的声明会在提升时进行初始化赋值为 undefined,因此访问这些变量的时候,不会报ReferenceError异常,而使用 let,const,class 声明的变量,被提升后不会被初始化,这些变量所处的状态被称为“temporal dead zone”,此时如果访问这些变量会抛出ReferenceError异常,看上去就像没被提升一样。

总结:

  1. js会将变量的声明提升到js顶部执行,因此对于这种语句:var a = 2;其实上js会将其分为var a;a = 2;两部分,并且将var a这一步提升到顶部执行。
  2. 变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。
  3. 当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。

扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!

【前端名狮】