ES6各大数据类型的扩展

作者: 追逐_e6cf | 来源:发表于2019-02-27 20:06 被阅读0次

    一、字符串扩展

    1. includes()startsWith()endsWith()。传统JavaScript只有indexOf()方法用来确定一个字符串是否包含在另一个字符串中,ES6又提供了三个新方法。
    • includes():返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
    let s = 'Hello world!';
    
    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true
    

    这三个方法都支持第二个参数,表示开始搜索的位置。但是使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

    let s = 'Hello world!';
    
    s.startsWith('world', 6) // true
    s.endsWith('Hello', 5) // true
    s.includes('Hello', 6) // false
    
    1. repeatrepeat方法返回一个新字符串,表示将原字符串重复n次。
    'x'.repeat(3) // "xxx"
    'hello'.repeat(2) // "hellohello"
    'na'.repeat(0) // ""
    
    • 参数如果是小数,会被向下取整。
    • 如果repeat的参数是负数或者Infinity,会报错。
    • 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0。
    • 参数NaN等同于 0。
    • 如果repeat的参数是字符串,则会先转换成数字。
    1. padStart()padEnd()。ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'
    
    'x'.padEnd(5, 'ab') // 'xabab'
    'x'.padEnd(4, 'ab') // 'xaba'
    
    • 如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
    • 如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
    • 如果省略第二个参数,默认使用空格补全长度。
    1. 模板字符串。
      模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
    • 如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
    • 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
    • 模板字符串中嵌入变量,需要将变量名写在${}之中。
    • 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
    • 模板字符串之中还能调用函数。
    • 如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。
    • 如果模板字符串中的变量没有声明,将报错。
    var name = "追逐",trait = "帅气";
    //es
    var str = "我叫"+name+",人非常"+trait+",说话又好听";
    
    //es6
    var str2 = `我叫 ${name} ,人非常 ${trait} ,说话又好听`;
    
    1. 标签模板。模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能。
    alert`123`
    // 等同于
    alert(123)
    

    标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

    let a = 5;
    let b = 10;
    
    tag`Hello ${ a + b } world ${ a * b }`;
    // 等同于
    tag(['Hello ', ' world ', ''], 15, 50);
    

    二、数值扩展

    1. Number.isFinite()Number.isNaN()。ES6 在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。
    • Number.isFinite()用来检查一个数值是否为有限的(finite)。
    Number.isFinite(15); // true
    Number.isFinite(0.8); // true
    Number.isFinite(NaN); // false
    Number.isFinite(Infinity); // false
    Number.isFinite(-Infinity); // false
    Number.isFinite('foo'); // false
    Number.isFinite('15'); // false
    Number.isFinite(true); // false
    
    • Number.isNaN()用来检查一个值是否为NaN。和全局函数 isNaN() 相比,该方法不会强制将参数转换成数字,只有在参数是真正的数字类型,且值为 NaN 的时候才会返回 true
    Number.isNaN(NaN);        // true
    Number.isNaN(Number.NaN); // true
    Number.isNaN(0 / 0)       // true
    
    // 下面这几个如果使用全局的 isNaN() 时,会返回 true。
    Number.isNaN("NaN");      // false,字符串 "NaN" 不会被隐式转换成数字 NaN。
    Number.isNaN(undefined);  // false
    Number.isNaN({});         // false
    Number.isNaN("blabla");   // false
    
    // 下面的都返回 false
    Number.isNaN(true);
    Number.isNaN(null);
    Number.isNaN(37);
    Number.isNaN("37");
    Number.isNaN("37.37");
    Number.isNaN("");
    Number.isNaN(" ");
    
    1. Number.parseInt()Number.parseFloat()。ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
    // ES5的写法
    parseInt('12.34') // 12
    parseFloat('123.45#') // 123.45
    
    // ES6的写法
    Number.parseInt('12.34') // 12
    Number.parseFloat('123.45#') // 123.45
    
    Number.parseInt === parseInt // true
    Number.parseFloat === parseFloat // true
    
    1. Number.isInteger()Number.isInteger()用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的储存方法,所以 3 和 3.0 被视为同一个值。
    Number.isInteger(25) // true
    Number.isInteger(25.0) // true
    Number.isInteger(25.1) // false
    Number.isInteger("15") // false
    Number.isInteger(true) // false
    
    1. Math对象的扩展。
      ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。
    • Math.trunc方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值。对于空值和无法截取整数的值,返回NaN
    Math.trunc(4.1) // 4
    Math.trunc(4.9) // 4
    Math.trunc(-4.1) // -4
    Math.trunc(-4.9) // -4
    Math.trunc(-0.1234) // -0
    
    • Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。它返回五种值,参数为正数,返回+1;参数为负数,返回-1;参数为 0,返回0;参数为-0,返回-0;其他值,返回NaN
    Math.sign(-5) // -1
    Math.sign(5) // +1
    Math.sign(0) // +0
    Math.sign(-0) // -0
    Math.sign(NaN) // NaN
    
    Math.sign('')  // 0
    Math.sign(true)  // +1
    Math.sign(false)  // 0
    Math.sign(null)  // 0
    Math.sign('9')  // +1
    Math.sign('foo')  // NaN
    Math.sign()  // NaN
    Math.sign(undefined)  // NaN
    
    • Math.cbrt方法用于计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
    Math.cbrt(-1) // -1
    Math.cbrt(0)  // 0
    Math.cbrt(1)  // 1
    Math.cbrt(2)  // 1.2599210498948734
    
    • Math.hypot方法返回所有参数的平方和的平方根。
    Math.hypot(3, 4);        // 5
    Math.hypot(3, 4, 5);     // 7.0710678118654755
    Math.hypot();            // 0
    Math.hypot(NaN);         // NaN
    Math.hypot(3, 4, 'foo'); // NaN
    Math.hypot(3, 4, '5');   // 7.0710678118654755
    Math.hypot(-3);          // 3
    
    1. 指数运算符。ES2016 新增了一个指数运算符(**)。指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
    2 ** 2 // 4
    2 ** 3 // 8
    
    let a = 1.5;
    a **= 2;
    // 等同于 a = a * a;
    
    let b = 4;
    b **= 3;
    // 等同于 b = b * b * b;
    

    三、函数扩展

    1. 函数参数的默认值。
    • 基本用法。
    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello
    
    • 与解构赋值默认值结合使用。
    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
    
    • 参数默认值的位置。通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
    // 例一
    function f(x = 1, y) {
      return [x, y];
    }
    
    f() // [1, undefined]
    f(2) // [2, undefined])
    f(, 1) // 报错
    f(undefined, 1) // [1, 1]
    
    // 例二
    function f(x, y = 5, z) {
      return [x, y, z];
    }
    
    f() // [undefined, 5, undefined]
    f(1) // [1, 5, undefined]
    f(1, ,2) // 报错
    f(1, undefined, 2) // [1, 5, 2]
    
    • 函数的length属性。指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。fn.length 返回形参个数,arguments.length 返回实参个数。
    (function (a) {}).length // 1
    (function (a = 5) {}).length // 0
    (function (a, b, c = 5) {}).length // 2
    
    • 作用域。一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
    var x = 1;
    
    function f(x, y = x) {
      console.log(y);
    }
    
    f(2) // 2
    

    上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2

    let x = 1;
    
    function f(y = x) {
      let x = 2;
      console.log(y);
    }
    
    f() // 1
    

    上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x

    var x = 1;
    
    function foo(x = x) {
      // ...
    }
    
    foo() // ReferenceError: x is not defined
    

    上面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。

    var x = 1;
    function foo(x, y = function() { x = 2; }) {
      var x = 3;
      y();
      console.log(x);
    }
    
    foo()//3
    console.log(x); //1
    

    如果将var x = 3var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

    var x = 1;
    function foo(x, y = function() { x = 2; }) {
      x = 3;
      y();
      console.log(x);
    }
    
    foo() // 2
    x // 1
    
    1. rest参数。
      ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3) // 10
    

    arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组push方法的例子。

    function push(array, ...items) {
      items.forEach(function(item) {
        array.push(item);
        console.log(item);
      });
    }
    
    var a = [];
    push(a, 1, 2, 3)
    

    注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

    // 报错
    function f(a, ...b, c) {
      // ...
    }
    

    函数的length属性,不包括 rest 参数。

    (function(a) {}).length  // 1
    (function(...a) {}).length  // 0
    (function(a, ...b) {}).length  // 1
    
    1. 严格模式。
      ES2016规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
    2. name属性,返回函数名。
    function foo() {}
    foo.name // "foo"
    
    var f = function () {};
    f.name // "f"
    
    var fn1 = function fn2(){};
    fn1.name // "fn2"
    
    1. 箭头函数
    • 基本用法,ES6 允许使用“箭头”(=>)定义函数。
    var f = v => v;
    //上面的箭头函数等同于
    var f = function(v) {
      return v;
    };
    

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

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

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

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

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

    // 报错
    let getTempItem = id => { id: id, name: "Temp" };
    
    // 不报错
    let getTempItem = id => ({ id: id, name: "Temp" });
    
    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
    • this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
    • 由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向。
    // ES6
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    // ES5
    function foo() {
      var _this = this;
    
      setTimeout(function () {
        console.log('id:', _this.id);
      }, 100);
    }
    //转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用外层的this。
    
    1. 函数参数的尾逗号。ES2017允许函数的最后一个参数有尾逗号。这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
    function clownsEverywhere(
      param1,
      param2,
    ) { /* ... */ }
    
    clownsEverywhere(
      'foo',
      'bar',
    );
    

    四、数组的扩展

    1. 扩展运算符是三个点(...)。它好比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>]
    

    扩展运算符后面还可以放置表达式。

    const arr = [
      ...(x > 0 ? ['a'] : []),
      'b',
    ];
    

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

    [...[], 1]
    // [1]
    

    由于扩展运算符可以展开数组,所以不再需要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);
    

    取一个数组中的最大值。

    //es5
    Math.max.apply(null,[1,5,2,8]) // 8
    //es6
    Math.max(...[1,5,2,8])//8
    //上面两种方法等同于
    Math.max(1,5,2,8)
    

    扩展运算符可以用于复制数组。

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

    扩展运算符用于合并数组。

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

    扩展运算符可以与解构赋值结合起来,用于生成数组。

    // ES5
    a = list[0], rest = list.slice(1)
    // ES6
    [a, ...rest] = list
    
    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];
    // 报错
    

    扩展运算符还可以将字符串转为真正的数组。

    [...'hello']
    // [ "h", "e", "l", "l", "o" ]
    

    扩展运算符实现了 Iterator 接口的对象

    let nodeList = document.querySelectorAll('div');
    let array = [...nodeList];
    
    1. Array.from()方法用于将两类对象转为真正的数组。
    • 第一个参数:一个类数组对象,用于转为真正的数组
    • 第二个参数:类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
    • 第三个参数:如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this
    // NodeList对象
    let ps = document.querySelectorAll('p');
    Array.from(ps).forEach(function (p) {
      console.log(p);
    });
    
    // arguments对象
    function foo() {
      var args = Array.from(arguments);
      // ...
    }
    
    1. Array.of()方法用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
    //Array
    Array() // []
    Array(3) // [, , ,]
    Array(3, 11, 8) // [3, 11, 8]
    
    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    
    1. 数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
      它接受三个参数。
    • target(必需):从该位置开始替换数据。
    • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
    • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
      这三个参数都应该是数值,如果不是,会自动转为数值。
    [1, 2, 3, 4, 5].copyWithin(0, 3)
    // [4, 5, 3, 4, 5]
    
    1. 数组实例的find()findIndex()。这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
    • 数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
    [1, 4, -5, 10].find((n) => n < 0)
    // -5
    [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    }) // 10
    //find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
    
    • findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
    [1, 5, 10, 15].findIndex(function(value, index, arr) {
      return value > 9;
    }) // 2
    
    1. 数组实例的fill()fill方法使用给定值,填充一个数组。fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]
    
    new Array(3).fill(7)
    // [7, 7, 7]
    
    1. for...of是es6引入的作为遍历所有数据结构的统一的方法。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
    2. 数组实例的 entries()keys()values()用于遍历数组,它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
    for (let index of ['a', 'b'].keys()) {
      console.log(index);
    }
    // 0
    // 1
    
    for (let elem of ['a', 'b'].values()) {
      console.log(elem);
    }
    // 'a'
    // 'b'
    
    for (let [index, elem] of ['a', 'b'].entries()) {
      console.log(index, elem);
    }
    // 0 "a"
    // 1 "b"
    
    1. 数组实例的includes()方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。
    [1, 2, 3].includes(2)     // true
    [1, 2, 3].includes(4)     // false
    [1, 2, NaN].includes(NaN) // true
    

    该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
    没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。

    1. 数组的空位指,数组的某一个位置没有任何值。
      空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
    0 in [undefined, undefined, undefined] // true
    0 in [, , ,] // false
    

    es5对空位的处理很不一致,大多数情况下会忽略空位。

    • forEach(), filter(), reduce(), every()some()都会跳过空位。
    • map()会跳过空位,但会保留这个值
    • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。
      es6明确将空位转为undefined
    • Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
    Array.from(['a',,'b'])
    // [ "a", undefined, "b" ]
    
    • 扩展运算符(...)也会将空位转为undefined
    [...['a',,'b']]
    // [ "a", undefined, "b" ]
    
    • copyWithin()会连空位一起拷贝。
    [,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
    
    • fill()会将空位视为正常的数组位置。
    new Array(3).fill('a') // ["a","a","a"]
    
    • for...of循环也会遍历空位。
    let arr = [, ,];
    for (let i of arr) {
      console.log(1);
    }
    // 1
    // 1
    

    上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。
    entries()keys()values()find()findIndex()会将空位处理成undefined

    // entries()
    [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
    
    // keys()
    [...[,'a'].keys()] // [0,1]
    
    // values()
    [...[,'a'].values()] // [undefined,"a"]
    
    // find()
    [,'a'].find(x => true) // undefined
    
    // findIndex()
    [,'a'].findIndex(x => true) // 0
    

    由于空位的处理规则非常不统一,所以建议避免出现空位。

    五、对象的扩展

    1. 属性的简洁表示法,ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
    //es6写法
    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    
    // 等同于es5写法
    const baz = {foo: foo};
    
    //es6写法
    const o = {
      method() {
        return "Hello!";
      }
    };
    
    // 等同于es5写法
    const o = {
      method: function() {
        return "Hello!";
      }
    };
    
    1. 属性名表达式
      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
    };
    

    表达式还可以用于定义方法名。

    let obj = {
      ['h' + 'ello']() {
        return 'hi';
      }
    };
    
    obj.hello() // hi
    

    注意,属性名表达式与简洁表示法,不能同时使用,会报错。

    // 报错
    const foo = 'bar';
    const bar = 'abc';
    const baz = { [foo] };
    
    // 正确
    const foo = 'bar';
    const baz = { [foo]: 'abc'};
    
    1. Object.is()。ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
      ES6 提出同值相等算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
    Object.is('foo', 'foo')
    // true
    Object.is({}, {})
    // false
    

    不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

    +0 === -0 //true
    NaN === NaN // false
    
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
    
    1. Object.assign()Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
    const target = { a: 1 };
    
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    console.log(target); // {a:1, b:2, c:3}
    

    如果只有一个参数,Object.assign会直接返回该参数。

    const obj = {a: 1};
    Object.assign(obj) === obj // true
    

    由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

    Object.assign(undefined) // 报错
    Object.assign(null) // 报错
    

    注意:Object.assign可以用来处理数组,但是会把数组视为对象。

    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
    //把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
    
    1. Object.keys()。ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
    var obj = { foo: 'bar', baz: 42 };
    Object.keys(obj)
    // ["foo", "baz"]
    

    ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for...of循环使用。

    let {keys, values, entries} = Object;
    let obj = { a: 1, b: 2, c: 3 };
    
    for (let key of keys(obj)) {
      console.log(key); // 'a', 'b', 'c'
    }
    
    for (let value of values(obj)) {
      console.log(value); // 1, 2, 3
    }
    
    for (let [key, value] of entries(obj)) {
      console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
    }
    
    1. Object.values()Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
    const obj = { foo: 'bar', baz: 42 };
    Object.values(obj)
    // ["bar", 42]
    

    返回数组的成员顺序,下面的代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是bca

    const obj = { 100: 'a', 2: 'b', 7: 'c' };
    Object.values(obj)
    // ["b", "c", "a"]
    
    1. Object.entries()Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
    const obj = { foo: 'bar', baz: 42 };
    Object.entries(obj)
    // [ ["foo", "bar"], ["baz", 42] ]
    
    1. 对象的扩展运算符。
    • 解构赋值。
    let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
    x // 1
    y // 2
    z // { a: 3, b: 4 }
    

    由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefinednull,就会报错,因为它们无法转为对象。

    let { x, y, ...z } = null; // 运行时错误
    let { x, y, ...z } = undefined; // 运行时错误
    

    解构赋值必须是最后一个参数,否则会报错。

    let { ...x, y, z } = obj; // 句法错误
    let { x, ...y, ...z } = obj; // 句法错误
    

    注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

    let obj = { a: { b: 1 } };
    let { ...x } = obj;
    obj.a.b = 2; 
    console.log(x.a.b); // 2
    
    • 扩展运算符
      扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
    let z = { a: 3, b: 4 };
    let n = { ...z };
    n // { a: 3, b: 4 }
    

    这等同于使用Object.assign方法。

    let aClone = { ...a };
    // 等同于
    let aClone = Object.assign({}, a);
    

    相关文章

      网友评论

        本文标题:ES6各大数据类型的扩展

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