• 【ES6】阮一峰ES6学习(一) let、const、解构赋值


    let 和 const 命令

    1. let

    1. 概念:块级作用域
    2. 不存在变量提升:在声明变量前使用该变量,会报错。
    3. 暂时性死区:形成了封闭作用域,在代码块内,使用let声明变量之前,该变量都是不可用的。在语法上成为 ”暂时性死区“(temporal dead zone,简称 TDZ)。
    if (true) {
      // TDZ开始
      tmp = 'abc'; // ReferenceError
      console.log(tmp); // ReferenceError
    
      let tmp; // TDZ结束
      console.log(tmp); // undefined
    
      tmp = 123;
      console.log(tmp); // 123
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
    4. 不允许重复声明:let允许在相同作用域内,重复声明同一个变量。

    2. 块级作用域

    1. 为什么需要块级作用域?

    第一种场景,内层变量可能会覆盖外层变量。

    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
    • 9

    上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

    第二种场景,用来计数的循环变量泄露为全局变量。(很常见)

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

    上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量

    2. 块级作用域

    let为 JavaScript 新增了块级作用域

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

    运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

    3. const

    1. 基本用法:const声明一个只读的常量。一旦声明,常量的值就不能改变,且必须赋初始值。
    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. const声明的变量不得改变值(指简单数据类型),这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
    2. const的作用域与let命令相同:只在声明所在的块级作用域内有效。
    3. const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
    4. const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,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

    上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

    变量的解构赋值

    1. 数组的解构赋值

    1. 基础用法

    以前,为变量赋值,只能直接指定值。

    let a = 1;
    let b = 2;
    let c = 3;
    
    • 1
    • 2
    • 3

    ES6 允许写成下面这样。

    let [a, b, c] = [1, 2, 3];
    
    • 1

    举多个例子

    let [foo, [[bar], baz]] = [1, [[2], 3]];
    foo // 1
    bar // 2
    baz // 3
    
    let [ , , third] = ["foo", "bar", "baz"];
    third // "baz"
    
    let [x, , y] = [1, 2, 3];
    x // 1
    y // 3
    
    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]
    
    let [x, y, ...z] = ['a'];
    x // "a"
    y // undefined
    z // []
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果解构不成功,变量的值就等于undefined

    let [foo] = [];
    // foo = undefined
    let [bar, foo] = [1];
    // foo = undefined
    
    • 1
    • 2
    • 3
    • 4

    另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

    let [x, y] = [1, 2, 3];
    x // 1
    y // 2
    
    let [a, [b], d] = [1, [2, 3], 4];
    a // 1
    b // 2
    d // 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对于 Set 结构,也可以使用数组的解构赋值。

    let [x, y, z] = new Set(['a', 'b', 'c']);
    x // "a"
    
    • 1
    • 2
    1. 默认值

    解构赋值允许指定默认值。

    let [foo = true] = [];
    foo // true
    
    let [x, y = 'b'] = ['a']; // x='a', y='b'
    let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 对象的解构赋值

    1. 基础用法
    let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
    foo // "aaa"
    bar // "bbb"
    
    • 1
    • 2
    • 3

    对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

    let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
    foo // "aaa"
    bar // "bbb"
    
    let { baz } = { foo: 'aaa', bar: 'bbb' };
    baz // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果解构失败,变量的值等于undefined

    对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

    // 例一
    let { log, sin, cos } = Math;
    
    // 例二
    const { log } = console;  // console.log
    log('hello') // hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量

    其实对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

    let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
    baz // "aaa"
    foo // error: foo is not defined
    
    • 1
    • 2
    • 3

    上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

    3. 字符串的解构赋值

    字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4. 数值和布尔值的解构赋值

    解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

    let {toString: s} = 123;
    s === Number.prototype.toString // true
    
    let {toString: s} = true;
    s === Boolean.prototype.toString // true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

    let { prop: x } = undefined; // TypeError
    let { prop: y } = null; // TypeError
    
    • 1
    • 2

    5. 用途

    (1)交换变量的值

    let x = 1;
    let y = 2;
    [x, y] = [y, x];
    
    • 1
    • 2
    • 3

    (2)从函数返回多个值

    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

    // 返回一个数组
    
    function example() {
      return [1, 2, 3];
    }
    let [a, b, c] = example();
    
    // 返回一个对象
    
    function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (3)提取 JSON 数据

    解构赋值对提取 JSON 对象中的数据,尤其有用。

    let jsonData = {
      id: 42,
      status: "OK",
      data: [867, 5309]
    };
    
    let { id, status, data: number } = jsonData;
    
    console.log(id, status, number);
    // 42, "OK", [867, 5309]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (4)遍历 Map 结构

    for...of遍历循环,配合变量的解构赋值,获取键名和键值就非常方便。

    const map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    
    for (let [key, value] of map) {
      console.log(key + " is " + value);
    }
    // first is hello
    // second is world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (5)输入模块的指定方法

    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

    const { SourceMapConsumer, SourceNode } = require("source-map");
    
    • 1
  • 相关阅读:
    【LVS】nat模式+dr模式+防火墙标签解决轮询错误
    多个微信怎么实现自动回复、自动通过好友自动打招呼?
    【毕设选题推荐】机器人工程专业毕设选题推荐
    介绍一个博客图片上传辅助工具
    【学习教程】建筑设计全过程碳排放计算与案例分析
    jQuery的extend方法仅仅是字面意思上的扩展吗?
    fcpx插件:82种复古电影胶卷框架和效果mFilm Matte
    linux系统定时任务与延迟任务
    Android 10.0 framework层实现app默认全屏显示
    Java中的IO流的缓冲流
  • 原文地址:https://blog.csdn.net/Bon_nenul/article/details/128188309