美文网首页Javascript
Javascript变量提升以及函数提升

Javascript变量提升以及函数提升

作者: 傅二毛 | 来源:发表于2019-10-14 01:29 被阅读0次

    引入

    下面的代码变片段会如何输出?

    // 代码段1
    function func1() {
        var a = 1;
        function a() {}
        console.log(a);
    }
    func1();
      
    // 代码段2
    function func2() {
        var a;
        function a() {}
        console.log(a);
    }
    func2();
    

    运行后,控制台第一个console.log(a)会输出1,而第二个console.log(2)则会输出函数a。结果如下:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    1
    [Function: a]
    
    [Done] exited with code=0 in 0.044 seconds
    
    

    Javascript运行时,会经过三个阶段^[《JavaScript预编译过程理解
    节选]:

    1. 语法分析阶段:语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误;
    2. 预编译阶段:预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数 ;
    3. 解释执行阶段:解释执行顾名思义便是执行代码了;

    Javascript具体执行过程:

    1. 页面产生便创建了GO全局对象(Global Object)(也就是window对象);
    2. 第一个脚本文件加载;
    3. 脚本加载完毕后,分析语法是否合法;
    4. 开始预编译
      • 查找变量声明,作为GO属性,值赋予undefined;
      • 查找函数声明,作为GO属性,值赋予函数体;

    【注意】

    • 预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译 ;
    • 只有在解释执行阶段才会进行变量初始化 ;
    1. 预编译两个小规则
      1. 函数声明整体提升—(具体点说,无论函数调用和声明的位置是前是后,系统总会把函数声明移到调用前面)
      2. 变量 声明提升—(具体点说,无论变量调用和声明的位置是前是后,系统总会把声明移到调用前,注意仅仅只是声明,所以值是undefined)
    2. 预编译前奏
      1. imply global 即任何变量,如果未经声明就赋值,则此变量就位全局变量所有。(全局域就是Window)
      2. 一切声明的全局变量,全是window的属性;var a=12;等同于Window.a = 12;
      3. 函数预编译发生在函数执行前一刻;

    变量提升

    本小节将注重体会Javascript预编译阶段,变量如何创建以及赋值的。如:

    // 定义两个变量
    var num1 = 50;
    var num2 = 100;
    

    在Javascript眼中代码是这样运行的。

    // 第一步:预编译阶段,将变量申明(定义)放在作用域最前面。
    var num1;
    var num2;
    // 第二步:执行赋值操作
    num1 = 50;
    num2 = 100;
    

    再通过一个例子理解下:

    function func(){
        var m = 10;
        console.log(m);
        console.log(n);
        var n = 20;
    }
    func();
    

    其实在Javascript眼中他是这样运行的:

    function func(){
        var m;
        var n;
        m = 10;
        console.log(m);
        console.log(n);
        n = 20;
    }
    

    此时控制台输出:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    10
    undefined
    
    [Done] exited with code=0 in 0.049 seconds
    

    下面通过两个函数同时运行,比较下控制台输出结果:

    function func(){
        var m = 10;
        console.log(m);
        console.log(n);
        var n = 20;
    }
    func();
    
    console.log();
    
    function funcT(){
        var m;
        var n;
        m = 10;
        console.log(m);
        console.log(n);
        n = 20;
    }
    
    funcT();
    

    此时控制台输出结果如下所示:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    10
    undefined
    
    10
    undefined
    
    [Done] exited with code=0 in 0.045 seconds
    

    两个函数的执行结果完全相同。所以 Javascript 并不是在定义一个变量的时候,声明完成之后立即赋值,而是把所有用到的变量全部声明之后,再到变量的定义的地方进行赋值,变量的声明的过程就是变量的提升。

    变量在声明提升的时候,是全部提升到作用域的最前面,一个接着一个的。但是在变量赋值的时候就不是一个接着一个赋值了,而是赋值的位置在变量原本定义的位置。原本js定义变量的地方,在js运行到这里的时候,才会进行赋值操作,而没有运行到的变量,不会进行赋值操作。

    所以变量的提升,提升的其实是变量的声明,而不是变量的赋值。

    函数提升

    首先还是可以通过一个例子体会下函数提升的过程是怎样的。

    function func(){
        console.log("func运行了");
    }
    
    // 申明前调用函数funcT();
    funcT();
    
    // 申明后调用函数func();
    func();
    
    function funcT(){
        console.log("funcT运行了");
    }
    

    此时控制台输出:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    funcT运行了
    func运行了
    
    [Done] exited with code=0 in 0.046 seconds
    

    此时两个函数都能成功执行。与变量提升不同的是,函数的提升是一步完成的。不同于变量提升是分成两步运行的,第一步是变量声明的提升,第二步是变量的赋值。函数的提升是直接将整个函数整体提升到作用域的最开始位置。

    变量提升与函数提升的顺序

    依旧通过例子体会:

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

    按照代码的执行顺序,从上而下,理论上控制台应该是先输出undefined1a(){}。但是实际控制台输出如下:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    # 先输出的是函数
    [Function: a]
    # 再输出1;
    1
    # 最后还是输出1
    1
    
    [Done] exited with code=0 in 0.051 seconds
    

    为什么会这样输出呢?其实在JS眼中,代码是这样的

    // 先发生变量提升,此时a如果能输出的话,肯定是输出undefined。
    var a;
    // 函数提升覆盖了定义的变量a的undefined,此时a;
    function a(){
        
    }
    // 提升结束后,此时遇到了第一个console.log()。故此时输出函数a
    console.log(a);
    // 带代码运行到var a=1时,a=1覆盖了函数提升阶段a的函数申明。
    a = 1;
    // 当代码运行到第二个console.log()的时候,故输出1;
    console.log(a);
    // 因为函数申明提前了,所以无法再去覆盖a的值。
    // 所以第三个console.log()输出的仍然是1。
    console.log(a);
    

    函数申明与函数表达式的顺序

    上面讲解了变量提升,以及函数提升,下面再来理解前节中所提:函数表达式<span style="color:red;font-weight:bolder">必须先定义方可调用!</span>函数申明<span style="color:red;font-weight:bolder">则既可先定义再调用,也可先调用再定义。</span>这句话的意思。

    仍然先看例子,下面的控制台如何输出?

    console.log(testFunc);
    
    console.log(func);
    
    testFunc();
    
    func();
    
    function testFunc(){
        console.log("testFunc()执行了");
    }
    
    var func = function (){
        console.log("func()执行了");
    }
    

    控制台输出:

    [Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
    [Function: testFunc]
    undefined
    testFunc()执行了
    /Users/ermao/Development/javascript_code/homework/demo.js:7
    func();
    ^
    
    # 这个地方就报错了,无法执行
    TypeError: func is not a function
        at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/demo.js:7:1)
        at Module._compile (internal/modules/cjs/loader.js:945:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
        at Module.load (internal/modules/cjs/loader.js:798:32)
        at Function.Module._load (internal/modules/cjs/loader.js:711:12)
        at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
        at internal/main/run_main_module.js:17:11
    
    [Done] exited with code=1 in 0.047 seconds
    

    从控制台输出可以看出,Javascript眼中代码是这样运行的:

    // 整个提升过程
    var func; // 先提升了变量
    // 再提升函数
    function testFunc(){
        console.log("testFunc()执行了");
    }
    
    // 代码执行阶段
    console.log(testFunc);  // 此时输出函数体
    console.log(func);      // 此时代码并没有运行到赋值阶段,所以还能输出undefined
    
    // 运行函数
    testFunc();             // 函数可以正常运行
    func();                 // 无法运行,因为在提升阶段是指当做变量提升了,并没有赋值。
                            // 所以func不能当做函数来运行
                            
    // 当执行到func 赋值时,为时已晚。报错后,无法继续运行了
    func = function(){
        console.log("func()执行了");
    }
    

    <span style="text-indent:2em;font-weight:bolder;color:red">就是说,函数提升仅适用于函数声明,而不适用于函数表达式。</span>简而言之,函数表达式在预编译提升阶段是当做变量进行提升的,并没有对变量进行赋值。

    那么箭头函数能成功运行吗?请参考下面的代码:

    console.log(testFunc);
    
    console.log(func);
    
    testFunc();
    
    func();
    
    function testFunc(){
        console.log("testFunc()执行了");
    }
    
    var func = () => {
        console.log("func()执行了");
    }
    

    控制台输出如下所示,原因呢?

    [Running] node "/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js"
    [Function: testFunc]
    undefined
    testFunc()执行了
    /Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7
    func();
    ^
    
    TypeError: func is not a function
        at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7:1)
        at Module._compile (internal/modules/cjs/loader.js:945:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
        at Module.load (internal/modules/cjs/loader.js:798:32)
        at Function.Module._load (internal/modules/cjs/loader.js:711:12)
        at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
        at internal/main/run_main_module.js:17:11
    
    [Done] exited with code=1 in 0.045 seconds
    

    相关文章

      网友评论

        本文标题:Javascript变量提升以及函数提升

        本文链接:https://www.haomeiwen.com/subject/tmllmctx.html