Js-变量提升,函数提升

理解变量提升and函数提升

Posted by ZXZ on April 25, 2019

JavaScript 初始化不会提升 变量声明不是必须的 常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端

变量提升

例子一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hoistVariable() {   //hoistVariable作用域

    if (!foo) {
        
        var foo = 5;    //这里的声明要提升到这个作用域最上面
        //引擎将变量声明提升到了函数顶部,初始值为undefined,自然,if语句块就会被执行,foo变量赋值为5
    }

    console.log(foo); // 5
}

hoistVariable();


预编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 预编译之后
function hoistVariable() {
    var foo;

    if (!foo) {
        foo = 5;          //声明提升
    }

    console.log(foo); // 5
}

hoistVariable();


例子二

1
2
3
4
5
6
7
8
9
10
11
12
var foo = 3;

function hoistVariable() {
    var foo = foo || 5;        //  A || B  表示A是true 的时候 值为前面的  false 的时候值是5
  
    console.log(foo); // 5
}

hoistVariable();


虽然外层作用域有个foo变量,但函数内是不会去引用的,因为预编译之后的代码逻辑是这样的:

预编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

var foo = 3;

// 预编译之后
function hoistVariable() {
    var foo;

    foo = foo || 5;     //这里的 foo是  undefined

    console.log(foo); // 5
}

hoistVariable();


例子三

如果当前作用域中声明了多个同名变量,那么根据我们的推断,它们的同一个标识符会被提升至作用域顶部,其他部分按顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hoistVariable() {
    var foo = 3;
    
    {
        var foo = 5;
    }

    console.log(foo); // 5
}

hoistVariable();


预编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 预编译之后
function hoistVariable() {
    var foo;

    foo = 3;
    
    {
        foo = 5;
    }

    console.log(foo); // 5
}

hoistVariable();


函数提升

函数可以在声明之前就可以调用,并且跟变量声明不同的是,它还能得到正确的结果,其实引擎是把函数声明整个地提升到了当前作用域的顶部

例子一

1
2
3
4
5
6
7
8
9
10
11
12
13

function hoistFunction() {
    foo(); // output: I am hoisted

    function foo() {
        console.log('I am hoisted');
    }
}

hoistFunction();


预编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 预编译之后
function hoistFunction() {
    function foo() {
        console.log('I am hoisted');    //变量提升只提升声明部分  比如  var foo=5   如果提升只提升  var foo
        //但是函数提升就不一样了   它是直接把整个函数提升到作用域顶部了
    }

    foo(); // output: I am hoisted
}

hoistFunction();


如果在同一个作用域中存在多个同名函数声明,后面出现的将会覆盖前面的函数声明

例子二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hoistFunction() {
    function foo() {
        console.log(1);
    }

    foo(); // output: 2

    function foo() {
        console.log(2);
    }
}

hoistFunction();

预编译之后

1
2
3
4
5
6
7
8
9
10
11
 function foo() {
        console.log(1);
    }

    function foo() {
        console.log(2);
    }

    foo(); // output: 2

对于函数,除了使用上面的函数声明,更多时候,我们会使用函数表达式,下面是函数声明和函数表达式的对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数声明
function foo() {
    console.log('function declaration');
}

// 匿名函数表达式
var foo = function() {    
    console.log('anonymous function expression');
};

// 具名函数表达式
var foo = function bar() {
    console.log('named function expression');
};

可以看到,匿名函数表达式,其实是将一个不带名字的函数声明赋值给了一个变量,而具名函数表达式,则是带名字的函数赋值给一个变量,需要注意到是,这个函数名只能在此函数内部使用。

我们也看到了,其实函数表达式可以通过变量访问,所以也存在变量提升同样的效果。如下实例三

例子三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hoistFunction() {
    foo(); // 2

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

    foo(); // 1

    function foo() {
        console.log(2);
    }

    foo(); // 1
}

hoistFunction();

因为JavaScript中的函数是一等公民,函数声明的优先级最高,会被提升至当前作用域最顶端,所以第一次调用时实际执行了下面定义的函数声明,然后第二次调用时,由于前面的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印同样的结果

预编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 预编译之后
function hoistFunction() {
    var foo;        //表示变量提升

    foo = function foo() {            //表示函数提升
        console.log(2);
    }

    foo(); // 2

    foo = function() {
        console.log(1);
    };

    foo(); // 1

    foo(); // 1
}

hoistFunction();


为什么要变量提升

函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。 这里简单阐述一下相互递归,下面两个函数分别在自己的函数体内调用了对方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 验证偶数
function isEven(n) {
    if (n === 0) {
        return true;
    }
    return isOdd(n - 1);    //如果没有函数提升编译到这一步时  isOdd函数还没有声明,所以就没办法调用isOdd函数
}

console.log(isEven(2)); // true

// 验证奇数
function isOdd(n) {
    if (n === 0) {
        return false;
    }
    return isEven(n - 1);
}

最佳实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var name = 'Scott';
var sayHello = function(guest) {
    console.log(name, 'says hello to', guest);   //这里的guest 就是提升后的
};

var i;
var guest;
var guests = ['John', 'Tom', 'Jack'];

for (i = 0; i < guests.length; i++) {
    guest = guests[i];
    
    // do something on guest

    sayHello(guest);
}