zoukankan      html  css  js  c++  java
  • 块级作用域

    块级作用域绑定

    传统上,Javascript变量声明机制一直令我们感到困惑。在大多数类C语言在声明变量的同时也会创建变量(绑定)是在声明发生的地方创建的。然而,在JavaScript中,情况并非如此。变量的实际创建位置取决于声明它们的方式,ECMAScript6提供了使控制范围更容易的方法。

    var 声明及变量提升(Hoisting)机制

    在函数作用域或全局作用域中通过关键字 var 声明的变量,无论实际上是在哪里声明的,都会被当成当前作用域顶部声明的变量,这就是我们常说的提升(Hoisting)机制。下面以一个函数为例来说明:

    function getValue(condition) {
      if (condition) {
        var value = "blue";
        // 其他代码
        return value;
      } else {
        // 此处可访问变量value,其值为undefined
        return null;
      }
      // 此处可访问变量value,其值为undefined
    }
    

    如果您不熟悉JavaScript,可能会认为只有当 condition 的值为true时才会创建变量 value 。事实上,不管怎样都会创建变量 value 。在编译阶段,JavaScript引擎将上面的 getValue 函数更改为如下所示:

    function getValue(condition) {
      var value;
      if (condition) {
        value = "blue";
        // 其他代码
        return value;
      } else {
        return null;
      }
    }
    

    变量 value 的声明被提升至函数顶部,而初始化操作依旧留在原处执行,这就意味着在 else 子句中也可以访问到该变量,且由于此时变量尚未初始化,所以其值为 undefined

    刚接触JavaScript的开发者通常会花一些时间习惯变量提升,有时还会因误解而导致程序忠出现bug。为此, ECMAScript6引入块级作用域来强化对变量生命周期的控制。

    块级声明

    块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(亦被称为词法作用域)存在于:

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

    很多类C语言都有块级作用域,而ECMAScript6引入块级作用域就是为了让JavaScriptg更灵活也更普适。

    let 声明

    let 声明语法与 var 的语法相同。基本上可以将 var 替换为 let 来声明一个变量,但将变量的作用域限制为只有当前的代码块(后面会讨论了其他一些细微的差异)。由于 let 声明不会被提升,因此通常会将 let 声明放在封闭代码的顶部,以便整个代码块都可以访问。这里有一个例子:

    function getValue(condition) {
      if (condition) {
        let value = "blue";
        // 其他代码
        return value;
      } else {
        // 变量value此处不存在
        return null;
      }
      // 变量value此处不存在
    }
    

    现在这个 getValue 函数的运行结果更像类C语言。变量 value 改由关键字 let 进行声明后,不再被提升至函数顶部。执行流离开 if 块,value 立即被销毁。如果 condition 的值为false,就永远不会声明并初始化 value

    禁止重复声明

    作用域中已经存在某个标识符,此时再使用 let 关键字声明它就会报错,举例来说:

    var count = 30;
    // 抛出语法错误
    let count = 40;
    

    如果当前作用域内嵌另一个作用域,是可以在内嵌作用域中使用 let 声明同名变量,示例:

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

    此处的let是在if块内声明了新变量count,因此不会抛出错误。内部块中的count会屏蔽全局作用域中的count,后者只有if块中才能访问到。

    const 声明

    使用 const 声明的是常量,其值一旦被设定后不可更改。因此,每个通过 const 声明的常量都必须初始化,示例如下:

    const maxItems = 30;
    // 语法报错,常量未初始化
    const name;
    

    const 与 let

    const 与 let 声明的都是块级标识符,所以常量也只在当前代码块内有效,一旦执行到块外就会立即销毁。常量同样也不会被提升至作用域顶部,示例如下:

    if (condition) {
      const maxItems = 5;
    }
    // 此处无法访问maxItems
    

    无论是使用var,还是let声明的标识符,在同一个作用域用const声明同名标识符也会抛出语法错误。举例来说:

    var message = 'Hello!'
    let age = 25;
    // 这两天语句都会抛出错误
    const message = 'Goodbye';
    const age = 30;
    

    const 声明与 let 声明有一个很大的不同,不可以为 const 定义的常量赋值,否则会抛出错误,例如:

    const maxItems = 5;
    // 抛出语法错误
    maxItems = 5;
    

    用 const 声明对象

    const 声明不允许修改绑定,但运行修改值。也就是说使用 const 声明对象后,可以修改该对象的属性值。举个例子:

    const person = {
      name: 'Nicholas'
    }
    person.name = 'Greg';
    // 抛出语法错误
    person = {
      name: 'Greg'
    }
    

    临时死区

    与 var 不同,let 和 const 声明的变量不会被提升至作用域顶部,如果在声明之前访问这些变量,即使是使用相对安全的 typeof 操作符也会触发引用报错,举个例子:

    if (condition) {
      console.log(typeof value); // 引用报错
      let value = 'blue';
    }
    

    由于 console.log(typeof value) 语句报错,因此后面的 let 声明变量 value 不会执行。此时的 value 还位于Javascript所谓的临时死区。

    但在 let 声明的作用域外对改变量使用 typeof 则不会报错,举个例子:

    console.log(typeof value); // undefined
    if (condition) {
      let value = 'blue';
    }
    

    循环中的块作用域绑定

    经常在 for 循环中使用 var i 声明变量

    for (var i = 0; i < 10; i++) {
      process(items[i])
    }
    console.log(i); // 10
    

    使用 let 声明变量就会有不同的结果:

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

    循环中的函数

    var 声明在循环中创建函数比较困难,因为变量到了循环外就不能访问了。例如:

    var funcs = [];
    
    for (var i = 0; i < 10; i++) {
      funcs.push(function( ) {
        console.log(i);
      });
    }
    
    funcs.forEach(function(func) {
      func(); // 输出10次数字10
    });
    

    为了解决这个问题,一般都是在循环中使用立即调用函数表达式(IIFE),就像这样:

    var funcs = [];
    
    for (var i = 0; i < 10; i++) {
      funcs.push((function(value) {
        return function( ) {
          console.log(value);
        }
      }(i)));
    }
    
    funcs.forEach(function(func) {
      func(); // 输出0,然后1、2,直到9
    });
    

    在循环内部,IIFE 表达式为接受每一个变量 i 创建了一个副本并存储了变量 value。这个变量的值就是相应迭代创建的函数所使用的值。

    循环中的 let 声明

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

    var funcs = [];
    
    for (let i = 0; i < 10; i++) {
      funcs.push(function( ) {
        console.log(i);
      });
    }
    
    funcs.forEach(function(func) {
      func(); // 输出0,然后1、2,直到9
    });
    

    对于 for-in 循环和 for-of 循环来说也是一样的,示例如下:

    var funcs = [],
        object = {
          a: true,
          b: true,
          c: true
        };
    
    for (let key in object) {
      funcs.push(function( ) {
        console.log(key);
      });
    }
    
    funcs.forEach(function(func) {
      func(); // 输出a、b和c
    });
    

    循环中的 const 声明

    对于普通的 for 循环来说,在初始化变量时使用 const,但是 i++ 更改这个变量的值就会抛出错误,就像这样:

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

    如果后续循环不会修改该变量,是可以使用 const 声明的。如下示例:

    var funs = [],
        object = {
          a: true,
          b: true,
          c: true
        };
    
    // 不会产生错误
    for (const key in object) {
      funcs.push(function( ) {
        console.log(key);
      });
    }
    
    funcs.forEach(function(func) {
      func(); // 输出a、b和c
    });
    

    全局作用域绑定

    当 var 被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。这意味着用 var 很可能无意中覆盖一个已经存在的全局属性,就像这样:

    // 在浏览器中
    var RegExp = 'Hello!';
    console.log(window.RegExp); // "Hello!"
    
    var ncz = 'Hi!';
    console.log(window.ncz); // "Hi!"
    

    在全局作用域中使用 let 或 const ,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。就是说,用 let 和 const 不会覆盖全局变量,而只能遮蔽它。示例如下:

    // 在浏览器中
    let RegExp = 'Hello!';
    console.log(Rx); // "Hello!"
    console.log((window.RegExp === RegExp); // false
    
    const ncz = 'Hi!';
    console.log(ncz); // "Hi!"
    console.log((window.ncz === ncz); // false
    

    如果希望在全集对象下定义变量,仍然可以用 var。这种情况常见于在浏览器中跨 frame 或跨 window 访问代码。

  • 相关阅读:
    机器学习为什么强大?
    将博客搬至CSDN
    nth_element()函数解决 第k小数
    DVWA XSS部分
    XSS挑战之旅(通过看代码解题)
    汇编语言(第三版)王爽 检测点3.2
    汇编语言(第三版)王爽 检测点3.1
    汇编语言(第三版)王爽 检测点2.3
    汇编语言(第三版)王爽 检测点2.2
    汇编语言(第三版)王爽 检测点2.1
  • 原文地址:https://www.cnblogs.com/xuzhenlei/p/12410328.html
Copyright © 2011-2022 走看看