关于变量提升的两三事

301 阅读6分钟

carbon _3_.png

近期有一道面试题比较火(上图是笔者参考题目加以修改的),笔者觉得很有趣就拿来发现一波,咋一看其实其中涉及到了好多的基础切底层的知识点。

任何声明在某个作用域的变量,都将附属在这个作用域中(函数作用域、块级作用域)。但是尽管变量都附属在同一作用域时也有一些特殊情况出现一些微妙的变化导致执行结果和你预期的截然不同,为此我们从基础说起。

    var age = 18;
    console.log(18)

这一段代码执行的结果一目了然,最终输出 18 。再看看下面这一段代码;

    age = 18;
    var age;
    console.log(age);// ?

应该会有人会自信不疑地觉得结果是 undefined,因为变量 age 本来有值可是被 初始化 了之后就变成 undefined 了。

可惜结果却不是这样的,结果是 18小朋友,你是不是有很多问好?

再看看下面这段代码

console.log(a);
var age = 18;

这种情况可能一些比较严谨的人就觉得会报错,因为 console.log(a) 的时候 a 还未声明,无法被获取到;很高兴会这样想,不过可惜你还是错了,因为这个时候代码不仅不会报错而且还可以正常输出。

这究竟是为什么呢,为什么这么“诡异”?在执行的过程中发生了什么?

编译器是如何处理上面的代码的?

在引擎执行代码的时候会拜托编译器先编译一下代码,转化成引擎可以读得懂的数据,这个时候 编译器 就会先找到所有变量的声明且将其一一对应的附属在各自的作用域中。

所以,在执行前编译将代码调整了一下,包括变量和函数在内的所有声明都会在代码执行前首先被处理;

所以在编译代码的时候,编译器 会将上面的代码分别作出对应的调整。

代码一

    var age;// >>>>> 被提升到顶部

    age = 18;
    console.log(age);// 18

代码二

var age;;// >>>>> 被提升到顶部

console.log(a);// a 声明了,但是没有赋值,故为 undefined
var age = 18;

这个声明过程中,变量 / 函数声明被提升到了顶部,这个过程便称之为变量提升。只有声明会被提升到顶部,赋值或者其他的逻辑依然保持原封不动;

函数声明优先级高于变量声明

console.log(typeof fun); // 'function'

var fun = 0;
function fun(){};

变量提升后的代码

var fun;
function fun(){};

console.log(typeof fun); // 'function'

var fun = 0;

这个时候会有同学脑洞打开,活学活用,会想到一下的处境;

console.log(typeof fun);
var fun = function fun(){}

可能会这样觉得这样的结果应该还是function,他的想法可能是这样的。

var fun;
function fun(){}
console.log(typeof fun);
fun = fun;

所以认为结果输入会是function,但是结果并不是这样,结果是undefined,因为这里涉及的不止只有变量提升的问题了,还涉及到函数声明和函数表达式的区别。

函数声明和函数表达式的区别

为了方便理解,将上述代码适当的变下形;

console.log(typeof fun); // undefined
console.log(typeof bar); // undefined
var fun = function bar(){}
Q&A

为什么 typeof funtypeof bar 都是 undefined呢?

typeof funundefined 是因为 var fun = function bar(){}这一步是赋值操作,赋值操作不会提升;

那么 typeof barundefined 是怎么回事呢?

在这里可以很明显地察觉到 bar 函数并未得到提升,是这样的,因为在函数表达式中,不过给变量赋值的是匿名函数还是具名函数都无法在所在的作用域中调用,所以结果才是undefined;

知道了函数声明和函数的表达式后你是否还会对下面这道题疑惑呢。


fun();
function fun(){
    console.log('欧拉欧拉~')
}

var fun = function (){
    console.log('木大木大~')
}

答案是 '欧拉欧拉~',如果还不了解建议继续往上翻重新阅读理解一遍消化一下。

接下来的知识点就劲爆了,小伙伴还记得顶部那一道面试题吗?那一道题笔者看了之后以为自己可以作对的,最后还是入坑了,过经过一番资料和书籍的查阅后基本了解其中的奥妙之处了。先看一下下面的例子。

var a = 1;
function fun(a){
    a = b =3;
}
fun();
console.log(a,b) // 1,3

上述的答案中,对于 a 的值 1 基本是没有什么问题,但是为什么 b 是 3 呢?在这里有和编译器有关了,编译器在函数作用域里面查询不到变量 b 便会向外层作用域逐一寻找一个叫 b 的变量,直到找到为止,如果找不到就会在全局自己声明一个叫 b 的变量。(非严格模式下)

上头了吗?接下来的内容增加劲爆哦!

var a = 1;

if(true){
    a = 2;
    function a(){}
}

console.log(a);

你们觉得最后的 a 是什么值呢?

  • 1
  • 2
  • function a(){}
  • undefined
  • a is not defined

笔者刚开始做的时候,我是这样理解的。

var a;
function a(){}
a = 1;
if(true){
    a = 2;
}
console.log(2)

自我感觉良好的选择了 2 ,如何满怀信心的打开浏览器的控制台,微微一笑地 cv + 回车 一波,最后啪啪啪啪被狠狠的打脸了!

纳尼,万维网式疑问 What? Why? Who? -- www;

然后不屑的去翻了尘封的书本,没有头绪,然后再去Gg 一波,最后终于知道了满意的答案了。因为现在很多浏览器都支持了 ES6 语法,在支持 ES6 的浏览器中:

  1. 允许在块级作用域中定义函数
  2. 函数声明实际上会将类似于使用var声明的函数表达式,函数名会提升至当前作用域顶部
  3. 同时函数声明也会保持在块级作用域的提升行为
  4. 执行到函数声明语句时,会把块作用域里的a赋值到全局同名变量 a,++这里的不能说全局,准确的说应该是外层作用域++

下面是笔者个人的理解

var a = 1;

if(true){
    //函数 a 被提升到当前块级作用域顶部
    function a(){}
    a = 2;
    //function a(){} 执行到函数语句,会把块级作用域中的 a 赋值给全局的 a
    window.a = a;
}

console.log(a);

接下来是重头戏了,终于到了最后的环节了

var a = 1;
var b = 2;

(function(){
    var b = 0;
    
    if(true){
    	console.log(a);
        b = 3;
        function b(){}
        b = 4;
        c = 5;
        console.log(b)
    }
    
    var a = 6;
    console.log(b)
})()

console.log(a,b,c)				

建议慢慢琢磨,先不要急着找到答案是什么。

var a = 1;
var b = 2;

// IIFE 函数自执行,函数体内拥有终于自己的块级作用域
(function(){
    //声明变量 b
    var b = 0;
    
    if(true){
    //因为 a 在底下声明了,故变量提升
    	console.log(a);//  undefined
    	//赋值 b
        b = 3;
        function b(){}//对外层作用域的 b 变量进行赋值操作
        b = 4;
        c = 5;
        console.log(b) // 4
    }
    
    var a = 6;
    console.log(b)// 3 
})()

console.log(a,b,c)	//1,2,5	

很多时候看是简单的题目,其实坑是最多的。