美文网首页
2.let和const命令(学习笔记)

2.let和const命令(学习笔记)

作者: 我才不是稻草人 | 来源:发表于2017-05-08 20:25 被阅读0次

    所有内容基于阮一峰ECMAScript 6 入门


    1.let命令

    只在命令所在的代码块内有效。
    for循环有一个特别之处,循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

    for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc
    

    不存在变量提升

    暂时性死区(temporal dead zone,简称TDZ)
    ar tmp = 123;
    
    if (true) {
      tmp = 'abc'; // ReferenceError
      let tmp;
    }
    
    // 报错
    let x = x;
    // ReferenceError: x is not defined
    

    在变量x的声明语句还没执行完成前,就去取x的值导致报错“x未定义”。


    不允许重复声明

    不能在函数内部重新声明参数

    function func(arg) {
      let arg; // 报错
    }
    
    function func(arg) {
      {
        let arg; // 不报错
      }
    }
    

    2.块级作用域

    为什么需要块级作用域

    1.内层变量可能会覆盖外层变量
    2.用来计数的循环变量泄露为全局变量

    ES6的块级作用域
    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }
    

    外层作用域无法读取内层作用域的变量。
    内层作用域可以定义外层作用域的同名变量。
    块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不必要了。

    // IIFE 写法
    (function (){
        var tmp = ...;
    }());
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    

    块级作用域与函数声明

    ES6中允许在块级作用域中声明函数。
    ES6中块级作用域内声明的函数类似与let,对作用域外没有影响。
    但是为了减轻因此产生的不兼容问题,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

    • 允许在块级作用域内声明函数
    • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
    • 同时,函数声明还会提升到所在的块级作用域的头部。
      上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当做let处理。
    // 浏览器的 ES6 环境
    function f() { console.log('I am outside!'); }
    
    (function () {
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());
    // Uncaught TypeError: f is not a function
    

    上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。

    // 浏览器的 ES6 环境
    function f() { console.log('I am outside!'); }
    (function () {
      var f = undefined;
      if (false) {
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());
    // Uncaught TypeError: f is not a function
    

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

    // 函数声明语句
    {
      let a = 'secret';
      function f() {
        return a;
      }
    }
    
    // 函数表达式
    {
      let a = 'secret';
      let f = function () {
        return a;
      };
    }
    

    另外,还有一个需要注意的地方,ES6允许块级作用域内声明函数的规则,只在使用大括号的情况下成立。

    // 不报错
    'use strict';
    if (true) {
      function f() {}
    }
    
    // 报错
    'use strict';
    if (true)
      function f() {}
    

    do 表达式

    本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
    现在有一个提案,使得块级作用域可以变为表达式,也即是说可以返回值:

    let x = do {
      let = f()
      t*t+1;
    };
    

    上面代码中,变量x会得到整个块级作用域的返回值。(只是提案,现在可能还用不了

    3.const 命令

    基本用法

    const声明一个只读的常量,const一旦声明就必须立即初始化不能留到以后赋值。
    const的作用域与let命令相同:只在声明所在块级作用域内有效。

    if (true) {
      const MAX = 5;
    }
    
    MAX // Uncaught ReferenceError: MAX is not defined
    

    const命令声明的常量也不是提升,同样存在暂时性死区,只能在声明的位置后面使用。
    也不能重复声明。

    本质

    const实际上保证的,不是变量的值不变,而是变量指向的那个内存地址不得改变。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。

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

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

    const foo = Object.freeze({});
    
    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;
    
    ES6声明变量的六种方法

    var命令function命令。
    ES6除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。

    4.顶层对象的属性

    顶层对象,浏览器环境指的是window对象,在Node指的是global对象。
    ES5之中,顶层对象的属性与全局变量是等价的。

    window.a = 1;
    a // 1
    
    a = 2;
    window.a // 2
    

    顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。
    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
    

    上面代码中,全局变量a是var声明的所以它是顶层对象的属性;全局变量b是由let声明,所以它不是顶层对象的属性,返回undefined。

    global对象

    ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

    • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
    • 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
    • Node 里面,顶层对象是global,但其他环境都不支持。

    同一段代码为了在各种环境都能取到顶层对象,现在一般是使用this,但是有局限性。

    • 全局环境中,this会返回顶层对象,但是,node模块和ES6模块中返回的是当前模块。
    • 函数里面的this,如果函数不是作为对象方法运行,而是单纯作为函数运行,this会指向顶层对象。但是在严格模式下,这是this会返回undefined。
    • 不管是严格模式,还是普通模式,new Function(’return this‘)(),总是会返回全局对象。但是,如果浏览器用来CSP(Content Security Policy,内容安全政策),那么eval,new Function这些方法可能都无法使用。
      下面是两种勉强可以在所有情况下取到顶层对象的方法。
    //方法一
    (typeof window !== 'undefined'
      ? window
      : (typeof process === 'object' &&
          typeof require === 'function' &&
          typeor 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');
    };
    

    现在有一个提案,在语言标准的层面,引入global
    作为顶层对象。也就是说,在所有环境下,global
    都是存在的,都可以从它拿到顶层对象。
    垫片库system.global
    模拟了这个提案,可以在所有环境拿到global

    // CommonJS的写法
    require('system.global/shim')();
    
    // ES6模块的写法
    import shim from 'system.global/shim'; shim();
    

    上面代码可以保证各种环境里面,global对象都是存在的。

    // CommonJS的写法
    var global = require('system.global')();
    
    // ES6模块的写法
    import getGlobal from 'system.global';
    const global = getGlobal();
    

    上面代码将顶层对象放入变量global。

    相关文章

      网友评论

          本文标题:2.let和const命令(学习笔记)

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