var let cosnt 详解

351 阅读7分钟
let 命令

ES6 新增了let命令,用来声明局部变量。它的用法类似于var,但是所声明的变量,只在let命令所在的 1代码块内有效,而且有2暂时性死区的约束。

关于使用var 如题

    var a = 99;            // 全局变量a
    f();                   // f是函数,虽然定义在调用的后面,但是函数声明会提升到作用域的顶部。 
    console.log(a);        // a=>99,  此时是全局变量的a
    function f() {
        console.log(a);      //当前的a变量是下面变量a声明提升后,默认值undefined
        var a = 10;
        console.log(a);      // a => 10
    }

    // 输出结果:
    undefined
    10
    99

    第一题
    { 
        var i = 9;
    } 
    console.log(i);  // 输出结果: 9
    第二题
    { 
        let i = 9;     // i变量只在 花括号内有效!!!
    } 
    console.log(i);  // 输出结果: ReferenceError
    第三题
    {
        let a = 10;
        var b = 1;
    }
    console.log(a)
    a // ReferenceError: a is not defined.
    console.log(b)
    b // 1

由上我们可以很清楚的得出,let是块级作用域,只在作用域内有效。

var let 遇到 for循环使用
    for (var i = 0; i <10; i++) {  
        setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
         console.log(i);        //执行此代码时,同步代码for循环已经执行完成
        }, 0);
    }
    // 输出结果十个 10

    //当使用let时 
    //i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
    for (let i = 0; i < 10; i++) { 
        setTimeout(function() {
            console.log(i);    //  i 是循环体内局部作用域,不受外界影响。
         }, 0);
    }
    打印结果 0 到 9 

let没有变量提升 与 暂时性死区

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    console.log(a);    // 错误:ReferenceError ...
    let a = 123;
    // 这里就可以安全使用a
    
    当使用var
    console.log(b); //打印结果undefined,所以var会出现变量提升
    var b = 123;
    console.log(b); //打印结果123
    
    暂时性死区
    
    var tmp = 123;
    if (true) {
      tmp = 'abc'; // ReferenceError,在块级作用域内已经多let前生成了死区范围当let或const前使用变量则会报错;
      let tmp;
    }
    // 不报错
    var x = x;
    console.log(x); undefined 变量提示后 相当于 把undefined 给了undedined

    // 报错
    let x = x;
    // ReferenceError: x is not defined

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

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

不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

    let不允许在相同作用域内重复声明同一变量
    function func() {
      let a = 10;
      var a = 1; 报错:'a' has already been declared
    }
    
    // 报错
    function func() {
      let a = 10;
      let a = 1;
    }
    
    因此,不能在函数内部重新声明参数。
    function func(arg) {
        let arg;
    }
    func() // 报错因为函数初始化时候已经相当于let了一个 arg,
            //如果 在次let arg;同一作用域重复声明就会报错;
            
    function func(arg) {
      {
        let arg;
      }
    }
    func() // 不报错
为什么需要块级作用域?
1 第一种场景,内层变量可能会覆盖外层变量。
1 用来计数的循环变量泄露为全局变量。
块级作用域
    function f1() {
        let n = 5;
        if (true) {
        let n = 10;
    }
        console.log(n); // 5
        
    function f1() {
        let var = 5;
        if (true) {
        let var = 10;
        }
        console.log(n); // 10
    }

块级作用域与函数声明 ``` // 情况一 if (true) { function f() {} }

    // 情况二
    try {
      function f() {}
    } catch(e) {
      // ...
    }

```

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

ES6 支持在块级作用域中声明 函数 但尽量用 函数表达

    // 块级作用域内部,优先使用函数表达式
{
    // 块级作用域内部的函数声明语句,建议不要使用
    {
      let a = 'secret';
      function f() {
        return a;
      }
    }

    // 块级作用域内部,优先使用函数表达式
    {
      let a = 'secret';
      let f = function () {
        return a;
      };
    }
}
const 命令

声明一个只读的常量。一旦声明,常量的值就不能改变。 const 和 let特性一样,

  • 1 只在块级作用域内有效,
  • 2 也不会发生变量提升
  • 3 同样存在暂时性死区。
  • 4 不可以重复声明
    const PI = 3.1415;
    PI // 3.1415
    PI = 3;
    // TypeError: Assignment to constant variable.

const本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

    const foo = {};

    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123

    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only
    上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
    
  

对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
ES6 声明变量的六种方法

ES6 声明变量的六种方法 ES5 只有两种声明变量的方法:var命令和function命令。

ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

let const class 等不是 顶层对象的属性

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

    var a = 1;
    // 如果在 Node 的 REPL 环境,可以写成 global.a
    // 或者采用通用方法,写成 this.a
    window.a // 1

    let b = 1;
    window.b // undefined
```