zoukankan      html  css  js  c++  java
  • 你不知道的JS之作用域和闭包(四)(声明)提升

      原文:你不知道的js系列

    先有鸡还是先有蛋?

    如下代码:

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

    很多开发者可能会认为结果会输出 undefined,因为 var a 在 a = 2 后面,好像变量似乎被重新定义了,所以结果会是默认值 undefined。

    然而,正确结果是 2.

    下面这段代码,a 在 定义之前被引用。不会抛出错误,也不会输出 2 。

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

    结果输出 undefined。

    编译器

    回忆一下第(一)节中的内容,引擎在解释执行之前会编译你的代码。

    所有的声明,包括函数和变量,都会在代码执行之前被处理。

    你看到 var a = 2;是一个语句,实际上 JavaScript 会认为这是两个语句,第一个声明 var a 在编译期间被处理,第二个语句赋值操作,留在原处等待执行。

    所以上面的第一段代码会被处理成两部分

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

    第二段代码实际上也被处理成:

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

    这个过程,可以看成是变量和函数的声明被提升到代码的头部,叫做变量提升(Hoisting)

    也就是说变量声明在赋值语句之前被执行。

    注:只有声明被提升,赋值和其它的操作被留在原处。

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

    上面代码中的函数 foo 的声明被提前,所以这样的调用是可以被执行的。

    提升发生在每个作用域内部,所有变量 a 的声明被提升到 foo 的顶部。相当于:

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

    函数声明被提升了,但是函数表达式不会被提升:

    foo(); // not ReferenceError, but TypeError!
    
    var foo = function bar() {
        // ...
    };

    这里的变量 foo 的声明被提升了,所以对 foo 的 RHS 查询不会报错。但这个时候 foo 的值还是 undefined,所有会报 TypeError 错误。

    就算是有命名的函数表达式,这个名称标识也不会被外部作用域访问:

    foo(); // TypeError
    bar(); // ReferenceError
    
    var foo = function bar() {
        // ...
    };

    上面这段代码会被解释为类似下面的:

    var foo;
    
    foo(); // TypeError
    bar(); // ReferenceError
    
    foo = function() {
        var bar = ...self...
        // ...
    }

    函数优先

    函数和变量的声明都会被提升,但是一个细微的区别就是函数声明在前面。

    考虑下面的代码:

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

    输出结果为1

    因为这段代码会被解释为:

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

    var foo 是一个重复的声明,所有被忽略掉了。即时它在函数声明的前面编写,因为函数声明的提升要先于变量声明的提升。

    和函数名称重复的变量声明就会被忽略,但如果有多个同名的函数声明,那么后面的声明会覆盖掉前面的:

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

    在普通的代码块中出现的函数声明也会被提升到外部作用域的顶部。

    foo(); // "b"
    
    var a = true;
    if (a) {
       function foo() { console.log( "a" ); }
    }
    else {
       function foo() { console.log( "b" ); }
    }

    但这种行为并不靠谱,因为有可能将来 JavaScript 的版本会改变这个机制(比如在块中声明的函数将会绑定在块作用域中),所以要避免在块中声明函数。

    小结

    变量声明提升,函数声明提升,其它操作不变

    函数表达式不会被提升

    函数声明优先于变量声明

    后声明的会覆盖先声明的

  • 相关阅读:
    A. Ivan the Fool and the Probability Theory
    关于消除“输出中最后的一个空格”问题
    半文件式输入中一种常见的输入方式
    持续输入问题
    汉诺塔问题
    给定两个正整数,求它们的最大公约数。
    第三届全国高校绿色计算机大赛(初赛题目)
    第三届全国高校绿色计算机大赛(初赛题目)
    C++中的输入及其原理简析
    流感传染
  • 原文地址:https://www.cnblogs.com/xiyouchen/p/10315867.html
Copyright © 2011-2022 走看看