美文网首页
块级作用域绑定

块级作用域绑定

作者: NnnLillian | 来源:发表于2019-10-11 17:19 被阅读0次

    在函数作用域或全局作用域中通过关键字var声明变量,但是无论在哪里声明,都会被当成在当前作用域顶部声明的变量,这就是提升(Hoisting)机制。

    运行如下代码,输出如右。

    console.log(x); // -> undefined
    var x = 5;
    console.log(x); // -> 5
    console.log(y); // -> Uncaught ReferenceError: y is not defined
    

    原因

    在预编译阶段,JavaScript引擎在运行代码前,重新调整代码结构,提升变量x的声明,修改成如下

    var x;
    console.log(x); // -> undefined
    x = 5;
    console.log(x); // -> 5
    console.log(y); // -> Uncaught ReferenceError: y is not defined
    

    为此,ECMAScript 6 引入块级作用域来强化对变量声明周期的控制。

    块级声明

    块级声明用于 声明 在指定块的作用域之外无法访问 的 变量。块级作用域存在于:

    • 函数内部
    • 块中(字符{和}之间的区域)

    let声明

    用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。

    禁止重声明

    假设作用域中已经存在某个标识符,再用let关键字声明,它就会报错。

    同一作用域中不能用let重复定义已经存在的标识符

    var a = 20;
    
    // 抛出语法错误
    let a = 40;
    

    但是如果当前作用域内嵌另一个作用域,便可以在内嵌的作用域中用let声明同名变量, 如下所示.

    var a = 20;
    
    // 不会抛出错误
    if(condition){
     let a = 40;
    }
    

    if块内声明的变量a会遮蔽全局作用域中的a, 全局作用域的a只能在if块外才能访问到.

    const声明

    使用const声明的是常量, 其值一旦被设定后不可更改. 因此, 每个通过const声明的常量必须进行初始化.

    • const与let声明的都是块级标识符, 所以常量也只有在当前代码块内有效, 一旦执行到块外会被立即销毁.
    • 与let相似, 在同一作用域用const声明已经存在的标识符也会导致语法错误.
    • 无论在严格模式还是非严格模式下, 都不可以为const定义的常量再赋值.
    • JavaScript中的常量如果是对象, 则对象的值可以修改

    针对以上四点中的最后两点举例说明:

    const x = 5;
    
    x = 6;// 抛出语法错误
    

    当用const声明对象时

    const person = {
     name: 'Lili'
    };
    
    // 修改对象属性的值
    person.name = 'Sue';  // 成功修改
    
    // 如下操作会抛出语法错误
    person = {
     name: 'Sue'
    };
    

    临时死区(Temporal Dead Zone)

    最常见的例子是:

    var tmp = 123;
    
    if (true) {
     tmp = 'abc'; // ReferenceError
     let tmp;
    }
    

    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

    临时死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

    也通过临时死区, 发现了之前的知识盲点:
    ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

    临时死区部分参考博客:

    循环中的块作用域绑定

    使用var, 由于var声明得到了提升,变量i在循环结束后仍可访问。

    for(var i = 0; i < 10; 1++){
       process(items[i])
    }
    // 这里仍可访问i
    console.log(i); // ->10
    

    使用let,变量i只存在于for循环中。

    for(let i = 0; i < 10; 1++){
       process(items[i])
    }
    // 这里不可访问i, 抛出一个错误。
    console.log(i);
    

    循环中的函数

    var items=[];
    
    for(var i = 0; i < 10; i++){
       items.push(function(){
           console.log(i);
       });
    }
    
    items.forEach(function(item){
       item();
    });
    

    这是因为循环里的每次迭代同时共享着变量i,循环内部创建的函数全都保留了对相同变量的引用,循环结束的时候变量i的值为10,所以每次调用console.log(i)时,就会输出数字10

    如果一定要用var,在循环中使用立即调用函数表达式(IIFE),以强制生成计数器变量的副本。
    输入:

    var items=[];
    
    for(var i = 0; i < 10; i++){
       items.push((function(value){
           return function(){
               console.log(value);
           }
       }(i)));
    }
    
    items.forEach(function(item){
       item();
    });
    

    在循环内部,IIFE表达式为接受每一个变量i都创建了一个副本并存储为变量value。

    循环中的let声明

    let声明模仿上述示例中IIFE所做的一切来简化循环过程,每次迭代循环都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。

    var items=[];
    
    for(let i = 0; i < 5; i++){
       items.push(function(){
           console.log(i);
       });
    }
    
    items.forEach(function(item){
       item();
    });
    

    每次循环的时候,let声明都会创建一个新变量i,并将其初始化为i的当前值,所以循环内部创建的每个函数都能得到属于它们自己的i的副本。

    对于for-in循环和for-of循环来说也是一样的。

    var items=[];
    var object={
          a:1,
          b:2,
          c:3,
       };
    
    for(let key in object){
      items.push(function(){
          console.log(key);
      });
    }
    
    items.forEach(function(item){
      item();
    });
    

    let声明在循环内部的行为是标准中专门定义的,它不一定与let的不提升特性相关。实际上,早期let实现不包含这一行为,它是后来加入的。

    循环中的const声明

    ES6中没有明确指明不允许在循环中使用const声明,然而针对不同类型的循环const会表现出不同的行为。

    对于普通的for循环来说,可以在初始化变量的时候使用const,但是更改这个变量就会抛出错误。

    var items=[];
    
    for(const i = 0; i < 10; i++){
       items.push(function(){
           console.log(i);
       });
    }
    

    在这段代码中,i被声明为常量。在循环的第一次迭代中,i=0,迭代执行成功。然后执行i++,该语句试图更改常量i,因此抛出错误。

    对于for-in循环和for-of循环中,使用const时的行为来说也是一样的。

    var items=[];
    var object={
          a:1,
          b:2,
          c:3,
       };
    
    for(const key in object){
      items.push(function(){
          console.log(key);
      });
    }
    
    items.forEach(function(item){
      item();
    });
    

    这段代码与“循环中的let声明”里描述for-in循环的代码几乎一模一样,唯一区别就是在循环内不能改变key的值

    之所以const可以用在for-in和for-of中,是因为每次迭代不会修改已有的绑定,而是创建一个新的绑定。

    全局块作用域绑定

    当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象的属性。这意味着var很有可能会无意中覆盖一个已经存在的全局属性。

    相关文章

      网友评论

          本文标题:块级作用域绑定

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