• ES6常用七大特性


      ES6可谓是对JS语言的一个颠覆性改变,增加了Module改善JS一直被诟病的模块化、Promise解决异步函数的回调地狱、Class的面相对象编程...

      在学习ES6的过程中,大家或多或少都有看过阮一峰老师的《ECMAScript 6 入门》。这本书把ES6的所有知识点都讲解的很详细,如果有时间,还是要去仔仔细细的研究一番。这篇博文只是摘录五个常用的特性来讲解,话不多说,下面开讲:

    一、let和const命令

    1、let:

    (1)基本用法

    ES6 新增了let命令,用来声明变量。类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

    {
    let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1

    (2)let在for循环中使用

    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[2](); // 10
    a[6](); // 10

    这个例子中,i 由 var 声明,在全局范围有效,a 的所有组员里面的 i 都是指向同一个 i,导致所有组员运行输出的都是最后一轮的 i 的值。

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

    这个例子中, i 由 let 声明,当前的 i 只在本轮循环有效,所有每次循环的 i 其实都是一个新的变量。
    Tips: 每轮循环的 i 都是重新声明的,JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的 i 时,就在上一轮循环的基础上进行计算

    (3)for循环的特别之处

    在 for 循环中,设置循环变量的那部分是一个父作用域,而循环内部是一个单独的子作用域

    for (let i = 0; i < 3; i++) {
        let i = 'abc';  // 和上一行for循环计数器的i不在同一作用域,所以可以声明成功。类比下面要讲的function(arg){let arg;}会报错,就是作用域问题
        console.log(i);
    }
    // abc
    // abc
    // abc

    (4)不能重复声明

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

    function func() {
      var a = 10;
      let a = 1;
    }
    func(); //Identifier 'a' has already been declared
    
    function func() {
      let a = 10;
      let a = 1;
    }
    func(); //Identifier 'a' has already been declared
     
    function func(arg) {
      let arg; 
    }
    func(); //Identifier 'arg' has already been declared
     
    function func(arg) {
      {
        let arg; 
      }
    }
    func(); //不报错

    (5)不存在变量提升

    var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined。 let 命令改变了语法行为,它所声明的变量一定要在声明后使用,否则会报错。

    console.log(foo); //undefined
    var foo = 2;
    console.log(bar); // ReferenceError: bar is not defined
    let bar =  2;

    (6)暂时性死区

    ES6规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前使用这些变量,就会报错。这在语法上称为 “暂时性死区”。

    var tmp = 123;
    if(true){
        tmp = 'abc'; // ReferenceError: tmp is not defined
        let tmp;
    }

    Tips:“暂时性死区”的本质就是:只要一进入当前作用域,所要使用的变量就已经存在了,但不可获取(否则报错),只有等到声明的那一行代码出现,才可以获取和使用。

     
    2、const:

    const命令的用法和let相似,最大不同点就是:const声明一个只读的常量。一旦声明,常量的值就不能改变。

    Tips:这个不可改变的是指针,所以对于const声明的对象和数组还是可以改变的。如果真的想将对象冻结,应该使用Object.freeze方法。

    3、let和const部分补充:

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

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

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

    ES6为了保持兼容性,规定:a. var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性。b. let,const,class 命令声明的全局变量,不属于顶层对象的属性。

    var a = 1;
    window.a; // 1
    let b = 2;
    window.b; // undefined

    二、变量的解构赋值

    ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    1、数组的解构赋值

    (1)基本用法

    // 之前,为变量赋值,可以直接指定值
    let a = 1;
    let b = 2;
    let c = 3;
    
    // ES6中允许这样
    let [a, b, c] = [1, 2, 3];  // 可以从数组中提取值,按照对应位置,对变量赋值。

    本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

    let [foo, [[bar], baz]] = [1, [[2], 3]];
    foo // 1
    bar // 2
    baz // 3
     
    let [ , , third] = ["foo", "bar", "baz"];
    third // "baz"
     
    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]
     
    let [x, y, ...z] = ['a'];
    x // "a"
    y // undefined  - 如果解构不成功,变量的值就等于 undefined
    z // []

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

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

    let [a, [b], d] = [1, [2, 3], 4];
    a // 1
    b // 2
    d // 4

    上面两个例子,都属于不完全解构,但是可以成功。

    如果等号的右边不是数组,那么将会报错。

    let [foo] = 1;
    let [foo] = false;
    let [foo] = NaN;
    let [foo] = undefined;
    let [foo] = null;
    let [foo] = {};

    (2)默认值

    解构赋值允许指定默认值。例如:

    let [foo = true] = [];
    foo;// true
     
    let [x, y = 'b'] = ['a'];
    x;//'a'
    y;//'b'

    Tips:ES6 内部使用严格相等运算符(===)去判断一个位置是否有值所以,只有当一个数组成员严格等于 undefined ,默认值才会生效。例如:

    let [x = 1] = [undefined];
    x // 1
    
    let [x = 1] = [null];
    x // null - 默认值就不会生效,因为null不严格等于undefined

    默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

    let [x = 1, y = x] = [1, 2];
    x;//1
    y;//2
     
    let [x = y, y = 1] = [];// ReferenceError: y is not defined -- let的暂时性死区
    let [x = y, y = 1] = [1, 2];
    2、对象的解构赋值

    (1)基本用法

    let { bar, foo } = { foo: "aaa", bar: "bbb" };
    foo // "aaa"
    bar // "bbb"
    
    let { baz } = { foo: "aaa", bar: "bbb" };
    baz // undefined

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

    如果没有与变量名对应的属性名,导致取不到值,最后等于undefined。

    如果变量名和属性名不一样,必须写才下面这样:

    let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
    baz // "aaa"
    
    let obj = { first: 'hello', last: 'world' };
    let { first: f, last: l } = obj;
    f // 'hello'
    l // 'world'

    这实际上说明,对象的解构赋值是下面形式的简写:

    let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

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

    let { foo: baz } = { foo: "aaa", bar: "bbb" };
    baz // "aaa"
    foo // error: foo is not defined

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

    (2)默认值

    对象的解构也可以指定默认值。默认值生效的条件是对象的属性值严格等于 undefined。

    let {x, y = 5} = {x: 1};
    x;// 1
    y;// 5
    
    let { message: msg = 'Something went wrong' } = {};
    msg;//"Something went wrong"

    Tips:如果要将一个已经声明的变量用于解构赋值,需要注意:

    let x;
    {x} = {x:1};
    //上面的代码会报错,因为JavaScript引擎会将 {x} 理解为一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。
     
    //正确的写法,将需要解构的部分用括号()包起来
    let x;
    ({x} = {x:1});
    3、字符串的解构赋值

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

    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"

    类似数组的对象都有一个 length 属性,因此还可以对这个属性解构赋值。

    let {length: len} = 'hello';
    len;// 5
    4、数值和布尔值的解构赋值

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

    let {toString: s} = 123;
    s === Number.prototype.toString // true
    
    let {toString: s} = true;
    s === Boolean.prototype.toString // true

    上面代码中,数值和布尔值的包装对象都有 toString 属性,因此变量 s 都可以取到值。

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

    5、函数参数的解构赋值
    function add([x, y]){
       return x + y;
    }
     
    add([1, 2]); // 3
     
    [[1, 2], [3, 4]].map(([a, b]) => a + b);
    // [ 3, 7 ]

     上面代码中,函数 add 的参数看似是一个数组,但在传入参数时,数组参数就解构为变量 x 和 y。对于函数内部代码来说,参数就是 x 和 y。

    函数参数的解构也可以使用默认参数:

    function move({x=0, y=0}={}) {
      return [x, y];
    }
    move({x:3,y:8});// [3, 8]
    move({x:3});// [3, 0]
    move({});// [0, 0]
    move();// [0, 0]

    上例中,函数 move 的参数是一个对象,通过对这个对象进行解构,得到变量 x 和 y 的值。如果解构失败,x 和 y 等于默认值。

    再看下面这种写法:

    function move({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
     
    move({x: 3, y: 8}); // [3, 8]
    move({x: 3}); // [3, undefined]
    move({}); // [undefined, undefined]
    move(); // [0, 0]

    上面的代码是为函数 move 的参数指定默认值,而不是为变量 x 和 y 指定默认值。

    6、解构的用途

    (1)交换变量的值

    let x = 1;
    let y = 2;
    [x, y] = [y, x];
    x;// 2
    y;// 1

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

    //返回一个数组
    function example() {
        return [1, 2, 3];
    }
    let [a, b, c] = example();
    a;//1
    b;//2
    c;//3
     
    //返回一个对象
    function example(){
        return {
            foo: 1,
            bar: 2
        }
    }
    let {foo, bar} = example();
    foo;//1
    bar;//2

    (3)函数参数的定义
    解构赋值可以方便的将一组参数与变量名对应起来。

    //参数是一组有序值
    function f([x, y, z]) {}
    f([1, 2, 3]);
     
    //参数是一组无序值
    function f({x, y, z}) {}
    f({x: 1, y: 2, z: 3});

    (4)提取JSON数据

    let data = {
        id: 1,
        status: 'ok',
        data: [867, 5612]
    };
    let {id, status, data:number} = data;
    console.log(id, status, number);// 1 "ok"  [867, 5612]

     (5)遍历Map解构

    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
    
    // 获取键名
    for (let [key] of map) {}
     
    // 获取键值
    for (let [,value] of map) {}

    三、函数的默认参数

    1、基本用法

    ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

    function log(x, y) {
      y = y || 'World';
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello World

    上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。


    为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

    if (typeof y === 'undefined') {
      y = 'World';
    }

    ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello -- 上面说过,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于 undefined ,默认值才会生效。
    2、与解构赋值默认值结合使用
    function foo({x, y = 5}) {
      console.log(x, y);
    }
    
    foo({}) // undefined 5
    foo({x: 1}) // 1 5
    foo({x: 1, y: 2}) // 1 2
    foo() // TypeError: Cannot read property 'x' of undefined

    上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。

    如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。

    通过提供函数参数的默认值,就可以避免这种情况。

    function foo({x, y = 5} = {}) {
      console.log(x, y);
    }
    
    foo() // undefined 5

    下面是另一个解构赋值默认值的例子:

    function fetch(url, { body = '', method = 'GET', headers = {} }) {
      console.log(method);
    }
    
    fetch('http://example.com', {})
    // "GET"
    
    fetch('http://example.com')
    // 报错

    上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。


    这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

    function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
      console.log(method);
    }
    
    fetch('http://example.com')
    // "GET"

    上面代码中,函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET。

    因此,与解构赋值默认值结合使用时,切记,函数要定义默认参数,防止函数不传参时报错。

    四、模板字符串

    模板字符串是增强版的字符串,用反引号 (`) 标识。它可以当普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

    1、基本用法
    let greeting = `\`Yo\` World!`;  // `Yo` World -- 使用反斜杠转义反引号
    
    // 多行字符串
    $('#result').append(`
      There are <b>${basket.count}</b> items
       in your basket, <em>${basket.onSale}</em>
      are on sale!
    `);

    Tips:
    a. 如果在模板字符串中需要使用反引号,则前面需要使用反斜杠转义。
    b. 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

    2、在模板字符串使用变量

    (1) 在模板字符串中嵌入变量,需要将变量名写在 ${} 之中。
    (2) 大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
    (3) 模板字符串之中还能调用函数。
    (4) 如果大括号中的值不是字符串,将按照一般的规则转为字符串。如: 如果大括号中是一个对象,将默认调用对象的 toString 方法。
    (5) 如果模板字符串中的变量没有声明将会报错。
    (6) 如果大括号内部是一个字符串,将原样输出。

    //普通字符串
    `In JavaScript '
    ' is a line-feed.`;
     `Hello ${'World'}`; // "Hello World"
    
    //字符串中嵌入变量
    let name = "Bob", time = "today";
    console.log(`Hello ${name}, how are you ${time}?`); //  Hello Bob, how are you today?
    
    //字符串中嵌入 JavaScript 表达式
    `${x} + ${y} = ${x + y}`;// "1 + 2 = 3"
    `${x} + ${y * 2} = ${x + y * 2}`;// "1 + 4 = 5"
    
    let obj = {x: 1, y: 2};
    `${obj.x + obj.y}`;// "3"
    
    // 字符串中嵌入函数
    function fn(){
        return "hello world"
    }
    `foo ${fn()} bar`;// "foo hello world bar"
    
    // 字符串中嵌入对象
    `${obj}`;// "[object Object]"
    
    // 字符串中嵌入未声明的变量
     `Hello, ${place}`;// ReferenceError: place is not defined 

    五、箭头函数

     ES6 允许使用 “箭头” (=>)定义函数。

    1、基本用法
    var f = v => v;
    //等同于
    var f = function (v) {
        return v;
    }

    => 前面的部分是函数的参数,=> 后面的部分是函数的代码块。

    (1)如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

    var f = () => 5;
    //等同于
    var f = function (v) {
        return 5
    };
    
    var sum = (num1, num2) =>  num1 + num2;
    //等同于
    var sum = function(num1, num2){
        return num1 + num2;
    }

    (2)如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。

    var sum = (num1,num2) => { return num1 + num2; }

    (3)由于大括号会被解释为代码块,所以如果箭头函数直接返回一个对象,就必须在对象外面加上括号,否则会报错。

    // 报错
    let getTempItem = id => { id: id, name: "Temp" };
    
    // 不报错
    let getTempItem = id => ({ id: id, name: "Temp" });
    getTempItem(1);//{id: 1, name: "Temp"}
    
    let getTempItem = id => {
        return { id: id, name: "Temp" };
    }
    getTempItem(1);// {id: 1, name: "Temp"}

    (4)箭头函数内部,可以嵌套箭头函数

    function insert (value) {
        return {into: function (array){
                return {after: function (afterValue) {
                            array.splice(array.indexOf(ahterValue)+1, 0, value);
                            return array;
                        }
                   }
            }    
        }
    }
    insert(2).into([1, 3]).after(1); //[1, 2, 3]
    
    //使用箭头函数改写
    let insert = (value) => ({into: (array) => ({after: (afterValue) => {
                array.splice(array.indexOf(afterValue)+1, 0, value);
                return array;
            }
        })
    });
    2、箭头函数与变量解构结合使用
    const full = ({ first, last }) => first + ' ' + last;
    full({first: 1, last: 2});// "1 2"
    // 等同于 function full(person) { return person.first + ' ' + person.last; } full({first: 1, last: 2});// "1 2"
    3、rest 参数与箭头函数结合
    const numbers = (...nums) => nums;
    numbers(1,2,3,4);//  [1, 2, 3, 4]
    
    const headAndTail = (head, ...tail) => [head, tail];
    headAndTail(1,2,3,4);// [1,[2,3,4]]
    4、箭头函数的优点

    (1)简化代码

    const isEven = n => n%2 == 0;
    isEven(3);// false
    isEven(4);// true

    (2)简化回调函数

    [1, 2, 3].map( x => x*x ); //[1, 4, 9]
    [3,2,5].sort((a, b) => a - b); // [2, 3, 5]
    5、箭头函数的注意点——this

    ES5中 this 的指向是可变的,但是箭头函数中 this 指向固定化。

    var  handler = {
        id: '123',
        init: function () {
            document.addEventListener('click', 
                event => this.doSomethisng(event.type), false);
        },
        doSomethisng: function (type) {
            console.log('Handling ' + type + ' for ' + this.id);
        }
    };
    handler.init();  //Handling click for 123  -- this指向handle  -- this是属于函数的一个对象,谁调用指向谁(es5中)

     

     this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数内部根本没有自己的this,导致内部的this 就是外层代码块的 this 。

     箭头函数的this是比较难理解的,但只要能想象到他是如何从ES5转化来的,就可以完全理解它的作用对象。 箭头函数转成 ES5 的代码如下:

    // ES6
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    // ES5
    function foo() {
      var _this = this;
    
      setTimeout(function () {
        console.log('id:', _this.id);
      }, 100);
    }

    Tips:

    (1) 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
    (2) 不可以当构造函数,即不可以使用 new 命令,因为它没有 this,否则会抛出一个错误。
    (3) 箭头函数没有自己的 this,所以不能使用 call()、apply()、bind() 这些方法去改变 this 指向。
    (4) 不可以使用arguments 对象,该对象在函数体内不存在。如果要用,可以使用rest参数代替。

    六、数组的扩展(扩展运算符) 

     1、扩展运算符

    (1)基本用法

      扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

    console.log(...[1, 2, 3])  // 1 2 3
    console.log(1, ...[2, 3, 4], 5)  // 1 2 3 4 5
    [...document.querySelectorAll('div')]  // [<div>, <div>, <div>]

      该运算符主要用于函数调用。扩展运算符与正常的函数参数可以结合使用,非常灵活。

    function push(array, ...items) {
      array.push(...items);
    }
    
    function add(x, y) {
      return x + y;
    }
    
    const numbers = [4, 38];
    add(...numbers) // 42
    
    function f(v, w, x, y, z) { }
    const args = [0, 1];
    f(-1, ...args, 2, ...[3]);

    如果扩展运算符后面是一个空数组,则不产生任何效果。 [...[], 1] // [1] 

    注意,扩展运算符如果放在括号中,JavaScript 引擎就会认为这是函数调用,否则就会报错。

    (...[1,2])
    // Uncaught SyntaxError: Unexpected number
    
    console.log((...[1,2]))
    // Uncaught SyntaxError: Unexpected number
    
    console.log(...[1,2])  // 不会报错 
    2、替代函数的apply方法

    由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

    // ES5 的写法
    function f(x, y, z) {
      // ...
    }
    var args = [0, 1, 2];
    f.apply(null, args);
    
    // ES6的写法
    function f(x, y, z) {
      // ...
    }
    let args = [0, 1, 2];
    f(...args);

    3、扩展运算符引用

    (1)复制数组

    数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针(浅拷贝),而不是克隆一个全新的数组。

    如何实现深拷贝呢?下面看看ES5和ES6的实现:

    // ES5
    const a1 = [1, 2];
    const a2 = a1.concat();
    
    // ES6
    const a1 = [1, 2];
    // 写法一
    const a2 = [...a1];
    // 写法二
    const [...a2] = a1;
    
    a2[0] = 2;
    a1 // [1, 2]

    (2)合并数组

    const arr1 = ['a', 'b'];
    const arr2 = ['c'];
    const arr3 = ['d', 'e'];
    
    // ES5 的合并数组
    arr1.concat(arr2, arr3);
    // [ 'a', 'b', 'c', 'd', 'e' ]
    
    // ES6 的合并数组
    [...arr1, ...arr2, ...arr3]
    // [ 'a', 'b', 'c', 'd', 'e' ]

    不过,这两种方法都是浅拷贝,使用的时候需要注意。如果修改了原数组的成员,会同步反映到新数组。

    const a1 = [{ foo: 1 }];
    const a2 = [{ bar: 2 }];
    
    const a3 = a1.concat(a2);
    const a4 = [...a1, ...a2];
    
    a3[0] === a1[0] // true
    a4[0] === a1[0] // true

    (3)与解构赋值结合

    const [first, ...rest] = [1, 2, 3, 4, 5];
    first // 1
    rest  // [2, 3, 4, 5]
    
    const [first, ...rest] = [];
    first // undefined
    rest  // []
    
    const [first, ...rest] = ["foo"];
    first  // "foo"
    rest   // []

    注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错
    
    const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错

    (4)字符串

    扩展运算符可以将字符串和函数参数arguments转成真正的数组

    [...'hello']  // [ "h", "e", "l", "l", "o" ]
    
    function (){
       let arr = [...arguments];
    }

    七、对象的扩展

    1、属性的简洁表示法

    ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    
    // 等同于
    const baz = {foo: foo};

    上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值

    属性简写:

    function f(x, y) {
      return {x, y};
    }
    
    // 等同于
    function f(x, y) {
      return {x: x, y: y};
    }
    
    f(1, 2) // Object {x: 1, y: 2}

    方法简写:

    const o = {
      method() {
        return "Hello!";
      }
    };
    
    // 等同于
    const o = {
      method: function() {
        return "Hello!";
      }
    };

     CommonJS 模块输出一组变量,就非常合适使用简洁写法。

    let ms = {};
    
    function getItem (key) {
      return key in ms ? ms[key] : null;
    }
    
    function setItem (key, value) {
      ms[key] = value;
    }
    
    function clear () {
      ms = {};
    }
    
    module.exports = { getItem, setItem, clear };
    // 等同于
    module.exports = {
      getItem: getItem,
      setItem: setItem,
      clear: clear
    };

    2、属性名表达式

    JavaScript 定义对象的属性,有两种方法。 

    // 方法一
    obj.foo = true;
    
    // 方法二
    obj['a' + 'bc'] = 123;

    上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

    但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。

    var obj = {
      foo: true,
      abc: 123
    };

    ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

    let propKey = 'foo';
    
    let obj = {
      [propKey]: true,
      ['a' + 'bc']: 123
    };

    表达式还可以用于定义方法名。比如我们熟悉的Vuex定义Mutation就是推荐这种方式:

    // mutation-types.js
    export const SOME_MUTATION = 'SOME_MUTATION'
    
    // store.js
    import Vuex from 'vuex'
    import { SOME_MUTATION } from './mutation-types'
    
    const store = new Vuex.Store({
      state: { ... },
      mutations: {
        // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
        [SOME_MUTATION] (state) {
          // mutate state
        }
      }
    })
    var test = {            
      'attr0' : 'attr1',            
      'attr1' : 'attr2',            
      'attr2' : 'attr3',            
      'attr3' : 'I'm here',          
    }                  

    // 输出: I'm here  , 方括号可以接受任何JS语句,最后都被转化为字符串。利用这个字符串在key-value集合中寻址。
    console.log(test[test[test[test['attr0']]]]);

    至此,先是粗略的讲解了一下ES6的七个常用的知识点。

    另外,ES6的Promise对象、async函数、Set和Map数据结构、Class、Module也都是比较具有颠覆性的改进,每一个都能满满的写一篇,待续...

     
  • 相关阅读:
    vue-cli3 set vue.config.js
    oracle 基本查询语句及实例
    输出流啊输入流
    java线程
    sql基础
    抽象类和接口
    重载和重写的区别
    简单的java面向对象计算器
    运算符的优先级
    隐式类型转换规则
  • 原文地址:https://www.cnblogs.com/chenwenhao/p/9974558.html
Copyright © 2020-2023  润新知