一、引入

  在了解这个知识点之前,我们先来看看下面的代码,控制台都会输出什么

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();

  小白理解:foo是一个全局变量,值为1,当执行bar函数的时候,对1取反的结果是false,不会执行bar函数内部的if语句,所以弹出1

  小炉:不不不,你并不知道变量提升和函数提升,请看下面正确的代码执行过程

var foo;
foo = 1 function bar(){ var foo; if(!foo){ foo = 10; } alert(foo); } bar();

  上面这段代码才是正确的执行顺序,在调用bar函数时,内部的foo产生了变量提升,提升到函数内部的顶端,又因为只是声明变量foo并未赋值,所以foo的值为undefined,取反则为true,然后if语句执行,将foo的值改为10,最后alert弹出10。这是为什么上面代码不输出1而输出10的原因,那么下面我们来介绍相关概念

二、从js预解析过程理解函数提升和变量提升

  1.预解析过程

    (1)把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。

    (2)把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。

    (3)先提升var,再提升function。(只有函数和变量同名时才是函数优先)

    (4)提升完后其他代码位置不变

    举个栗子

    例1:
    var a = 25;
    function abc() {
        alert(a);
        var a = 10;
    }
    abc();

   预解析后如下:
    var a;
    function abc() {
        var a;
        alert(a);
        a = 10;
    }
    a = 25;
    abc();  //ndefined;

    从字面上理解就是变量和函数的声明会移动到移动到函数或者全局代码的开头位置,先提升var,再提升function,提升完成后不改变其他代码位置

  2.函数提升

    函数提升,只会提升函数声明,而不会提升函数表达式。

f();
fn();

// 函数表达式
var fn = function(){
    console.log(1);
}

// 函数声明
function f(){
    console.log(0);
}

    真实的执行过程是怎么样的呢?看下面

// 函数声明
function f(){
    console.log(0)
}
var fn;
f();
fn();
fn = function(){
    console.log(1);
}

    所以在fn()时就会报错,说fn不是一个函数。也证明了函数提升,只会提升函数声明,而不会提升函数表达式。

  3.变量提升

console.log(a) // undefined
var a = 'hello JS'

/* 在我们声明a之前为什么输出a不会报错呢? 不急,让我们接着往下看 */

num = 6;
num++;
var num;
console.log(num) // 7 为什么给一个还没有声明的变量赋值会不报错呢

  上面的代码能够正确执行的原因都是因为变量提升,将变量提升到当前作用域的最前面,由于并未赋值,所以上面第一个console会打出undefined

三、同名函数和变量提升时怎么办?

  1.函数和变量同名

    举个栗子

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

    上面的代码最终结果是打印出1,同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效。但是赋值语句不会消失,所以赋值为1。

  2.函数同名

    举个栗子

fn();//2
function fn() { console.log(1); }
fn();//2
var fn = 10;
fn();//报错 fn is not a function
function fn() { console.log(2); }
fn();

    函数同名时,后面声明的函数会覆盖前面的函数

  3.变量同名

    (1)全局变量和函数内部变量同名

var a = 1;
function test(x) {
 alert(x);//undefined
 alert(a);//undefined
var a = 2;
alert(a);//2
 }
test();

    代码执行过程:首先声明变量a和test函数,然后给a赋值为1,调用函数test,因为没有给test传参,x为undefined,又因为函数内部有var声明的变量a,存在变量提升,所以先弹出a为undefined,再给a赋值为2,再弹出2。

    我们可以看到,局部变量的优先级高于同名的全局变量 。如果在函数中声明一个局部变量同名,则全局变量就会被局部变量覆盖。

    (2)同一环境下的两个同名变量,后一个会覆盖前一个

四、由函数提升和变量提升引出的作用域问题

  1.javascript是没有块级作用域的,函数是javascript中唯一拥有自身作用域的结构

  2.声明变量,实际上就是定义了一个名字,在内存中开辟了存储空间,并且初始为undefined,提升到当前作用域顶部,只提升变量,不提升所赋的值

  3.块内的变量声明和函数声明也会被提升,例如if语句。

  4.局部变量的优先级高于同名的全局变量,当在自身作用域内找不到该变量的时候,会沿着作用域链逐步向上查找,若在全局作用域内部仍找不到该变量,则会抛出异常。

五、如何取消函数提升和变量提升

  ES6中引入了块级作用域的概念,也有let、const、class等新的声明方式来避免函数提升和变量提升带来的问题(ES6语法传送门

六、总结

  1.函数声明和变量声明会被提升到作用域的顶部,只提升声明,提升完成后其他代码位置不改变

  2.同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效

  3.同名变量、同名函数后声明的会覆盖前面的

  4.块内的变量声明和函数声明也会被提升,例如if语句。

  5.函数声明和函数表达式相比,函数声明使用可以更加自由,可以放在随意的位置,因为它能够整体的变量提升。而函数表达式使用就相对没有那么自由了,调用必须在声明的后面,因为变量提前只是将表达式的变量提前,并没有将表达式的内容提前。

  6.局部变量的优先级高于同名的全局变量

参考文档:https://www.cnblogs.com/nangezi/p/9105778.html

     https://blog.csdn.net/liuqiao0327/article/details/106971270/

     https://juejin.im/post/6844904179371081741#heading-0

08-01 20:07