• 2.let 和 const 命令


    let在哪里声明,就只能在哪里用

    {
      let a = 10;
      var b = 1;
    }
    a // ReferenceError: a is not defined.
    b // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    var的bug

    for (let i = 0; i < 10; i++) {
      // ...
    }
    console.log(i);
    // ReferenceError: i is not defined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    let可以解决上面的问题

    变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量

    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
    函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)。

    for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    var有变量提升,let没有

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    let声明有暂时性死区

    只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
    存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
    在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    var tmp = 123;
    if (true) {
      tmp = 'abc'; // ReferenceError
      let tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    “暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

    typeof x; // ReferenceError
    let x;
    
    • 1
    • 2

    作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。
    undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

    typeof undeclared_variable // "undefined"
    
    • 1

    调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。如果y的默认值是x,就不会报错,因为此时x已经声明了。

    function bar(x = y, y = 2) {
      return [x, y];
    }
    bar(); // 报错
    
    • 1
    • 2
    • 3
    • 4
    function bar(x = 2, y = x) {
      return [x, y];
    }
    bar(); // [2, 2]
    
    • 1
    • 2
    • 3
    • 4

    也是暂时性死区

    let x = x;
    // ReferenceError: x is not defined
    
    • 1
    • 2

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

    let 不允许重复声明

    // 报错
    function func() {
      let a = 10;
      var a = 1;
    }
    
    // 报错
    function func() {
      let a = 10;
      let a = 1;
    }
    
    function func(arg) {
      let arg;
    }
    func() // 报错
    
    function func(arg) {
      {
        let arg;
      }
    }
    func() // 不报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    块级作用域

    ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景
    变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

    var tmp = new Date();
    function f() {
      console.log(tmp);
      if (false) {
        var tmp = 'hello world';
      }
    }
    f(); // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    计数的循环变量泄露为全局变量

    var s = 'hello';
    for (var i = 0; i < s.length; i++) {
      console.log(s[i]);
    }
    console.log(i); // 5
    
    • 1
    • 2
    • 3
    • 4
    • 5

    let实际上为 JavaScript 新增了块级作用域。

    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ES6 允许块级作用域的任意嵌套

    {{{{
      {let insane = 'Hello World'}
      console.log(insane); // 报错
    }}}};
    
    • 1
    • 2
    • 3
    • 4

    内层作用域可以定义外层作用域的同名变量。

    {{{{
      let insane = 'Hello World';
      {let insane = 'Hello World'}
    }}}};
    
    • 1
    • 2
    • 3
    • 4

    块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    块级作用域与函数声明

    ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明,所以下面两种是非法的

    // 情况一
    if (true) {
      function f() {}
    }
    
    // 情况二
    try {
      function f() {}
    } catch(e) {
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    下面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

    • 允许在块级作用域内声明函数。
    • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
    • 同时,函数声明还会提升到所在的块级作用域的头部。
      在这里插入图片描述

    考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

    // 块级作用域内部的函数声明语句,建议不要使用
    {
      let a = 'secret';
      function f() {
        return a;
      }
    }
    
    // 块级作用域内部,优先使用函数表达式
    {
      let a = 'secret';
      let f = function () {
        return a;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

    // 第一种写法,报错
    if (true) let x = 1;
    
    // 第二种写法,不报错
    if (true) {
      let x = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    const声明只读常量。

    一旦声明,常量的值就不能改变。

    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    const声明的变量不得改变值,所以,const一旦声明变量,就必须立即初始化

    const foo;
    // SyntaxError: Missing initializer in const declaration
    
    • 1
    • 2

    const保证的,不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

    const foo = {};
    
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123
    
    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    const a = [];
    a.push('Hello'); // 可执行
    a.length = 0;    // 可执行
    a = ['Dave'];    // 报错
    
    • 1
    • 2
    • 3
    • 4

    递归冻结就可以实现真正的不可更改

    const foo = Object.freeze({});
    
    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    var命令和function命令声明的全局变量,依旧是顶层对象的属性;
    let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

    var a = 1;
    // 如果在 Node 的 REPL 环境,可以写成 global.a
    // 或者采用通用方法,写成 this.a
    window.a // 1
    
    let b = 1;
    window.b // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    globalThis

    1. 浏览器有window和self
    2. Web Worker 有self
    3. Node 有global
    4. 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined
    5. 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
    6. 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

    在这里插入图片描述

    在这里插入图片描述

    综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

    // 方法一
    (typeof window !== 'undefined'
       ? window
       : (typeof process === 'object' &&
          typeof require === 'function' &&
          typeof global === 'object')
         ? global
         : this);
    
    // 方法二
    var getGlobal = function () {
      if (typeof self !== 'undefined') { return self; }
      if (typeof window !== 'undefined') { return window; }
      if (typeof global !== 'undefined') { return global; }
      throw new Error('unable to locate global object');
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
    在这里插入图片描述

  • 相关阅读:
    文盘Rust -- 给程序加个日志
    熬夜也要肝完的阿里内部面试官手册,吃透直接拿下大厂心仪offer
    6、Mybatis-Plus wrapper的使用
    React-Hooks用法:useState()、useEffect()、useRef()
    最近带着江苏高校的学生做软件测试项目实战
    电脑安装了ubnutu20.04双系统以后,卡死在grub界面里
    Python Opencv实践 - 图像的距(Moments,Hu Moments)
    java文件查看大小,压缩,文件下载工具类
    如何利用CodeQL挖掘CVE-2020-10199
    User Account Status 在CDB 和PDB不一致的情况 OPEN & IN ROLLOVER
  • 原文地址:https://blog.csdn.net/formylovetm/article/details/127439065