zoukankan      html  css  js  c++  java
  • javascript的变量声明提升

    本文学习自:http://openwares.net/js/javascript_declaration_hoisting.html

    https://bonsaiden.github.io/JavaScript-Garden/zh/两篇博文

    JavaScript 有两种变量:全局变量和局部变量。

    如果在任何函数定义之外声明了一个变量,则该变量为全局变量,且该变量的值在整个持续范围内都可以访问和修改。

    • 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。
    • 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。

    这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的

    如果在函数定义内声明了一个变量,则该变量为局部变量。每次执行该函数时都会创建和破坏该变量;且它不能被该函数外的任何事物访问。

    局部变量一定要以var申明,否则是全局变量。

    每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError异常。

    隐式的全局变量

    // 脚本 A
    foo = '42';
    
    // 脚本 B
    var foo = '42'
    

    上面两段脚本效果不同。脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo。再次强调,上面的效果完全不同,不使用 var 声明变量将会导致隐式的全局变量产生。

    // 全局作用域
    var foo = 42;
    function test() {
        // 局部作用域
        foo = 21;
    }
    test();
    foo; // 21
    

    在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 起初这看起来并不是大问题,但是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG。

    // 全局作用域
    var items = [/* 数组 */];
    for(var i = 0; i < 10; i++) {
        subLoop();
    }
    
    function subLoop() {
        // subLoop 函数作用域
        for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
            // 干活
        }
    }
    

    外部循环在第一次调用 subLoop 之后就会终止,因为 subLoop 覆盖了全局变量 i。 在第二个 for 循环中使用 var 声明变量可以避免这种错误。 声明变量时绝对不要遗漏 var 关键字,除非这就是期望的影响外部作用域的行为。

    局部变量

    JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var 关键字声明。

    // 全局变量
    var foo = 1;
    var bar = 2;
    var i = 2;
    
    function test(i) {
        // 函数 test 内的局部作用域
        i = 5;
    
        var foo = 3;
        bar = 4;
    }
    test(10);
    

    foo 和 i 是函数 test 内的局部变量,而对 bar 的赋值将会覆盖全局作用域内的同名变量

    函数本身的作用域

    函数本身也是一个值,也有自己的作用域。它的作用域绑定其声明时所在的作用域。

    var a = 1;

    var x = function () {

      console.log(a);

    };

    function f() {

      var a = 2;

      x();

    }

    f() ;  // 1

    上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。

    很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

    var x = function (){

      console.log(a);

    };

    function y(f){

      var a = 2;

      f();

    }

    y(x);  // ReferenceError: a is not defined

    上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。

    javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面。

    先看一段代码

    var v = "hello";

    (function(){

      console.log(v);

      var v = "world";

    })();

    这段代码运行的结果是什么呢?
    答案是:undefined
    这段代码说明了两个问题:
    第一,function作用域里的变量v遮盖了上层作用域变量v。代码做少些变动,如下所示:

    var v = "hello";

    if(true){

      console.log(v);

      var v = "world";

    }

    输出结果为”hello”,说明javascript是没有块级作用域的。函数是JavaScript中唯一拥有自身作用域的结构。

    第二,在function作用域内,变量v的声明被提升了。所以最初的代码相当于:

     
    结论:javascript是没有块级作用域的,函数是JavaScript中唯一拥有自身作用域的结构。
     

    声明、定义与初始化

    声明即宣称一个名字的存在,定义则为这个名字分配存储空间,而初始化则是为名字分配的存储空间赋初值。

    javascript中则是这样:

    var v;//声明变量v
    v = "hello";//(定义并)初始化变量v

    因为javascript为动态语言,其变量并没有固定的类型,其存储空间大小会随初始化与赋值而变化,所以其变量的“定义”就不像传统的静态语言一样了,其定义显得无关紧要。

    var v = "hello";

    (function(){

      var v; //declaration hoisting

      console.log(v);

      v = "world";

    })();

    声明提升——当前作用域内的声明都会提升到作用域的最前面,包括变量和函数的声明

    (function(){
      var a = "1";
      var f = function(){};
      var b = "2";
      var c = "3";
    })();

    变量a,f,b,c的声明会被提升到函数作用域的最前面,类似如下:

    (function(){
      var a,f,b,c;
      a = "1";
      f = function(){};
      b = "2";
      c = "3";
    })();

    请注意函数表达式并没有被提升,这也是函数表达式与函数声明的区别。进一步看二者的区别:

    (function(){
      //var f1,function f2(){}; //hoisting,被隐式提升的声明
      f1(); //ReferenceError: f1 is not defined
      f2();
      var f1 = function(){};
      function f2(){}
    })();

    上面代码中函数声明f2被提升,所以在前面调用f2是没问题的。虽然变量f1也被提升,但f1提升后的值为undefined,其真正的初始值是在执行到函数表达式处被赋予的。所以只有声明是被提升的。

    名字解析顺序

    javascript中一个名字(name)以四种方式进入作用域(scope),其优先级顺序如下:
    1、语言内置:所有的作用域中都有 this 和 arguments 关键字
    2、形式参数:函数的参数在函数作用域中都是有效的
    3、函数声明:形如function foo() {}
    4、变量声明:形如var bar;

    名字声明的优先级如上所示,也就是说如果一个变量的名字与函数的名字相同,那么函数的名字会覆盖变量的名字,无论其在代码中的顺序如何。但名字的初始化却是按其在代码中书写的顺序进行的,不受以上优先级的影响。看代码:

    (function(){
        var foo;
        console.log(typeof foo); //function  
        function foo(){}
        foo = "foo";
        console.log(typeof foo); //string
    })();

    如果形式参数中有多个同名变量,那么最后一个同名参数会覆盖其他同名参数,即使最后一个同名参数并没有定义。

    以上的名字解析优先级存在例外,比如可以覆盖语言内置的名字arguments。

    命名函数表达式

    可以像函数声明一样为函数表达式指定一个名字,但这并不会使函数表达式成为函数声明。命名函数表达式的名字不会进入名字空间,也不会被提升。

    f();//TypeError: f is not a function
    foo();//ReferenceError: foo is not defined
    var f = function foo(){console.log(typeof foo);};
    f();//function
    foo();//ReferenceError: foo is not defined

    命名函数表达式的名字只在该函数的作用域内部有效。

  • 相关阅读:
    互动留言赠书:《Oracle高性能系统实战大全》
    超融合硬件损坏导致Oracle RAC异常恢复实录
    架构师不得不了解的硬件知识
    加班做的可视化被老板嫌弃,是因为你不会用这些数据工具
    iOS开发之七:常用控件--UISlider、UISegmentedControl、UIPageControl的使用
    iOS开发之六:常用控件--UIImageView的使用
    iOS开发之五:常用控件--UITextField的使用
    Objective-C实现常用的4种排序算法
    C语言实现4种常用排序
    面试常用的4种数组排序
  • 原文地址:https://www.cnblogs.com/wqhwe/p/5216581.html
Copyright © 2011-2022 走看看