ES6笔记

作者: 5df463a52098 | 来源:发表于2019-01-09 16:58 被阅读18次

    一、let和const

    二、变量的解构赋值

    三、字符串的扩展

    1、字符串遍历

    for (let k of 'hello') {
      console.log(k)
    }
    let text = String.fromCodePoint(0x20BB7);
    for (let i = 0; i < text.length; i++) {
      console.log(text[i]);
    }
    // " "
    // " "
    for (let i of text) {
      console.log(i);
    }
    

    除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

    2、includes()、startsWith()、endsWith()

    let a = 'hello world!'
        console.log(a.includes('o'))//true
        console.log(a.startsWith('he'))//true
        console.log(a.endsWith('!'))//true
        console.log(a.includes('d', 1))//false
        console.log(a.startsWith('d', 2))//false
        console.log(a.endsWith('h', 1))//true
    

    第二个参数表示开始搜索的位置到字符串结束,endsWith不同,它针对的是前n个字符。

    3、repeat()

    'hello'.repeat(2) // "hellohello"
    'hello'.repeat(0) // ""
    'hello'.repeat(2.9) // "hellohello"
    'hello'.repeat(-1) // 报错
    'hello'.repeat(Infinity) // 报错
    'hello'.repeat(-0.8) // “”
    'hello'.repeat(NaN) // “”
    'hello'.repeat('aa') // “”
    'hello'.repeat('2') // “hellohello”
    

    参数是小数,会被取整;
    参数是负数或者Infinity,会报错;
    参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0;
    参数NaN是等同于 0;
    参数是字符串,则会先转换成数字。

    4、padStart(),padEnd()

    padStart()用于头部补全,padEnd()用于尾部补全。

    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'
    'x'.padEnd(5, 'ab') // 'xabab'
    'x'.padEnd(4, 'ab') // 'xaba'
    'xxx'.padStart(2, 'ab') // 'xxx'
    'xxx'.padEnd(2, 'ab') // 'xxx'
    'xxx'.padStart(5, 'abcd') // 'abxxx'
    'xxx'.padEnd(5, 'abcd') // 'xxxab'
    'xxx'.padStart(5) // '  xxx'
    'xxx'.padEnd(5) // 'xxx  '
    

    一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
    如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
    如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
    如果省略第二个参数,默认使用空格补全长度。
    用途:
    padStart()的常见用途是为数值补全指定位数。另一个用途是提示字符串格式。

    '12'.padStart(10, '0') // "0000000012"
    '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
    

    5、matchAll()

    matchAll方法返回一个正则表达式在当前字符串的所有匹配

    6、模版字符串

    // 字符串中嵌入变量
    let name = "Bob", time = "today";
    `Hello ${name}, how are you ${time}?`
    

    大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,引用对象属性,调用函数。

    四、正则的扩展

    1、RegExp构造函数

    var regex = new RegExp('xyz', 'i');
    // 等价于
    var regex = /xyz/i;
    var regex = new RegExp(/xyz/i);
    // 等价于
    var regex = /xyz/i;
    new RegExp(/abc/ig, 'i').flags
    

    参数是字符串,这时第二个参数表示正则表达式的修饰符(flag);
    参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。
    如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符,原有正则对象的修饰符是ig,它会被第二个参数i覆盖。

    2、字符串的正则方法(match()、repalce()、search()、split())

    3、u修饰符

    4、RegExp.prototype.unicode 属性

    正则实例对象新增unicode属性,表示是否设置了u修饰符

    const r1 = /hello/;
    const r2 = /hello/u;
    r1.unicode // false
    r2.unicode // true
    

    5、y修饰符

    ‘粘连’(sticky)修饰符
    y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

    var s = 'aaa_aa_a';
    var r1 = /a+/g;
    var r2 = /a+/y;
    r1.exec(s) // ["aaa"]
    r2.exec(s) // ["aaa"]
    r1.exec(s) // ["aa"]
    r2.exec(s) // null
    

    lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。

    const REGEX = /a/g;
    // 指定从2号位置(y)开始匹配
    REGEX.lastIndex = 2;
    // 匹配成功
    const match = REGEX.exec('xaya');
    // 在3号位置匹配成功
    match.index // 3
    // 下一次匹配从4号位开始
    REGEX.lastIndex // 4
    // 4号位开始匹配失败
    REGEX.exec('xaya') // null
    
    <-----!----->
    const REGEX = /a/y;
    
    // 指定从2号位置开始匹配
    REGEX.lastIndex = 2;
    
    // 不是粘连,匹配失败
    REGEX.exec('xaya') // null
    
    // 指定从3号位置开始匹配
    REGEX.lastIndex = 3;
    
    // 3号位置是粘连,匹配成功
    const match = REGEX.exec('xaya');
    match.index // 3
    REGEX.lastIndex // 4
    

    6、RegExp.prototype.sticky 属性

    ES6 的正则实例对象多了sticky属性,表示是否设置了y修饰符。

    var r = /hello\d/y;
    r.sticky // true
    

    7、RegExp.prototype.flags 属性

    ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符。

    // ES5 的 source 属性
    // 返回正则表达式的正文
    /abc/ig.source
    // "abc"
    
    // ES6 的 flags 属性
    // 返回正则表达式的修饰符
    /abc/ig.flags
    // 'gi'
    

    8、s 修饰符:dotAll 模式

    9、后行断言

    10、Unicode 属性类

    11、具名组匹配

    const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
    
    const matchObj = RE_DATE.exec('1999-12-31');
    const year = matchObj[1]; // 1999
    const month = matchObj[2]; // 12
    const day = matchObj[3]; // 31
    

    组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。

    const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
    
    const matchObj = RE_DATE.exec('1999-12-31');
    const year = matchObj.groups.year; // 1999
    const month = matchObj.groups.month; // 12
    const day = matchObj.groups.day; // 31
    

    ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用.

    12、String.prototype.matchAll

    五、数值的扩展

    1、二进制和八进制表示法

    ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
    如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。

    Number('0b111')  // 7
    Number('0o10')  // 8
    

    2、Number.isFinite(), Number.isNaN()

    ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
    Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity;如果参数类型不是数值,Number.isFinite一律返回false。
    Number.isNaN()用来检查一个值是否为NaN;如果参数类型不是NaN,Number.isNaN一律返回false。

    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) // true
    Number.isNaN(15) // false
    Number.isNaN('15') // false
    Number.isNaN(true) // false
    Number.isNaN(9/NaN) // true
    Number.isNaN('true' / 0) // true
    Number.isNaN('true' / 'true') // true
    

    它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。

    isFinite(25) // true
    isFinite("25") // true
    Number.isFinite(25) // true
    Number.isFinite("25") // false
    
    isNaN(NaN) // true
    isNaN("NaN") // true
    Number.isNaN(NaN) // true
    Number.isNaN("NaN") // false
    Number.isNaN(1) // false
    

    3、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
    

    这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

    4、Number.isInteger()

    Number.isInteger()用来判断一个数值是否为整数。

    Number.isInteger(25) // true
    Number.isInteger(25.1) // false
    Number.isInteger(25.0) // true
    Number.isInteger(true) // false
    Number.isInteger(null) // false
    Number.isInteger('12') // false
    Number.isInteger() // false
    Number.isInteger(3.0000000000000002) // true
    

    注意,由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。总之,如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数。

    5、Number.EPSILON

    ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。

    Number.EPSILON === Math.pow(2, -52)
    // true
    Number.EPSILON
    // 2.220446049250313e-16
    

    6、安全整数和 Number.isSafeInteger()

    JavaScript 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值。

    Math.pow(2, 53) === Math.pow(2, 53) + 1
    // true
    

    超出 2 的 53 次方之后,一个数就不精确了.
    ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

    Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
    // true
    Number.MAX_SAFE_INTEGER === 9007199254740991
    // true
    
    Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
    // true
    Number.MIN_SAFE_INTEGER === -9007199254740991
    // true
    

    Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

    Number.isSafeInteger(9007199254740990) // true
    Number.isSafeInteger(9007199254740992) // false
    
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
    

    7、Math 对象的扩展

    (1)Math.trunc方法用于去除一个数的小数部分,返回整数部分。

    Math.trunc(4.9) // 4
    Math.trunc(-4.1) // -4
    Math.trunc('123.456') // 123
    Math.trunc(true) //1
    Math.trunc(false) // 0
    Math.trunc(null) // 0
    Math.trunc(NaN);      // NaN
    Math.trunc('foo');    // NaN
    Math.trunc();         // NaN
    Math.trunc(undefined) // NaN
    

    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.trunc = Math.trunc || function(x) {
      return x < 0 ? Math.ceil(x) : Math.floor(x);
    };
    

    (2)Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
    它会返回五种值。
    ----参数为正数,返回+1;
    ----参数为负数,返回-1;
    ----参数为 0,返回0;
    ----参数为-0,返回-0;
    ----其他值,返回NaN。
    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.sign = Math.sign || function(x) {
      x = +x; // convert to a number
      if (x === 0 || isNaN(x)) {
        return x;
      }
      return x > 0 ? 1 : -1;
    };
    

    (3)Math.cbrt方法用于计算一个数的立方根。
    对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。

    Math.cbrt(2)  // 1.2599210498948734
    Math.cbrt('8') // 2
    Math.cbrt('hello') // NaN
    

    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.cbrt = Math.cbrt || function(x) {
      var y = Math.pow(Math.abs(x), 1/3);
      return x < 0 ? -y : y;
    };
    

    (4)Math.clz32()方法将参数转为 32 位无符号整数的形式,然后这个 32 位值里面有多少个前导 0。

    Math.clz32(0) // 32
    Math.clz32(1) // 31
    Math.clz32(1000) // 22
    Math.clz32(0b01000000000000000000000000000000) // 1
    Math.clz32(0b00100000000000000000000000000000) // 2
    Math.clz32() // 32
    Math.clz32(NaN) // 32
    Math.clz32(Infinity) // 32
    Math.clz32(null) // 32
    Math.clz32('foo') // 32
    Math.clz32([]) // 32
    Math.clz32({}) // 32
    Math.clz32(true) // 31
    

    对于小数,Math.clz32方法只考虑整数部分。
    对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算。
    (5)Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
    如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)与a * b的结果是相同的,即该方法等同于(a * b)|0的效果(超过 32 位的部分溢出)。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

    Math.imul(2, 4)   // 8
    Math.imul(-1, 8)  // -8
    Math.imul(-2, -2) // 4
    

    (6)Math.fround方法返回一个数的32位单精度浮点数形式。
    (7)Math.hypot方法返回所有参数的平方和的平方根。
    如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。

    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
    

    (8)对数方法:
    ES6 新增了4 个对数方法:
    ----Math.expm1()
    Math.expm1(x)返回 e^{x}- 1,即Math.exp(x) - 1。
    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.expm1 = Math.expm1 || function(x) {
      return Math.exp(x) - 1;
    };
    

    ----Math.log1p()
    Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。
    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.log1p = Math.log1p || function(x) {
      return Math.log(1 + x);
    };
    

    ----Math.log10()
    Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。
    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.log10 = Math.log10 || function(x) {
      return Math.log(x) / Math.LN10;
    };
    

    ----Math.log2()
    Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。
    对于没有部署这个方法的环境,可以用下面的代码模拟。

    Math.log2 = Math.log2 || function(x) {
      return Math.log(x) / Math.LN2;
    };
    

    (9)双曲函数方法
    ES6 新增了 6 个双曲函数方法。
    ----Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
    ----Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
    ----Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
    ----Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
    ----Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
    ----Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)
    (10)指数运算符
    ES2016 新增了一个指数运算符()。
    这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的.
    指数运算符可以与等号结合,形成一个新的赋值运算符(
    =)。

    // 相当于 2 ** (3 ** 2)
    2 ** 3 ** 2
    // 512
    let b = 4;
    b **= 3;
    // 等同于 b = b * b * b;
    

    六、函数的扩展

    1、函数参数的默认值

    ES6允许函数参数指定默认值

    function log(x, y = 'World') {
      console.log(x, y);
    }
    

    注意:参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

    let x = 99;
    function foo(p = x + 1) {
      console.log(p);
    }
    foo() // 100
    x = 100;
    foo() // 101
    

    2、函数length属性

    指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

    (function (a) {}).length // 1
    (function (a = 5) {}).length // 0
    (function (a, b, c = 5) {}).length // 2
    

    3、作用域

    4、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
    

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

    function(a) {}).length  // 1
    (function(...a) {}).length  // 0
    (function(a, ...b) {}).length  // 1
    

    5、严格模式

    从 ES5 开始,函数内部可以设定为严格模式。
    S2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

    // 报错
    function doSomething(a, b = a) {
      'use strict';
      // code
    }
    
    // 报错
    const doSomething = function ({a, b}) {
      'use strict';
      // code
    };
    
    // 报错
    const doSomething = (...a) => {
      'use strict';
      // code
    };
    
    const obj = {
      // 报错
      doSomething({a, b}) {
        'use strict';
        // code
      }
    };
    

    6、name属性

    函数的name属性,返回该函数的函数名。

    function foo() {}
    foo.name // "foo"
    
    var f = function () {};
    // ES5
    f.name // ""
    // ES6
    f.name // "f"
    
    const bar = function baz() {};
    // ES5
    bar.name // "baz"
    // ES6
    bar.name // "baz"
    

    注意:ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
    如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
    Function构造函数返回的函数实例,name属性的值为anonymous。

    (new Function).name // "anonymous"
    

    bind返回的函数,name属性值会加上bound前缀。

    function foo() {};
    foo.bind({}).name // "bound foo"
    (function(){}).bind({}).name // "bound "
    

    7、箭头函数

    ES6 允许使用“箭头”(=>)定义函数。
    如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
    使用注意点
    箭头函数有几个使用注意点。
    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
    注意:
    this对象的指向是可变的,但是在箭头函数中,它是固定的。内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
    不适用场合:
    定义函数的方法,且该方法内部包括this;

    const cat = {
      lives: 9,
      jumps: () => {
        this.lives--;
      }
    }
    

    需要动态this的时候,也不应使用箭头函数。

    var button = document.getElementById('press');
    button.addEventListener('click', () => {
      this.classList.toggle('on');
    });
    

    8、双冒号运算符

    函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。用来取代call、apply、bind调用。
    如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
    如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法.

    foo::bar;
    // 等同于
    bar.bind(foo);
    
    foo::bar(...arguments);
    // 等同于
    bar.apply(foo, arguments);
    
    var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;
    

    9、尾调用优化

    尾调用是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

    function f(x){
      return g(x);
    }
    

    函数f的最后一步是调用函数g,这就叫尾调用。
    ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
    func.arguments:返回调用时函数的参数。
    func.caller:返回调用当前函数的那个函数。
    尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

    function restricted() {
      'use strict';
      restricted.caller;    // 报错
      restricted.arguments; // 报错
    }
    restricted();
    

    10、尾递归

    尾调用自身,就称为尾递归

    function factorial(n) {
      if (n === 1) return 1;
      return n * factorial(n - 1);
    }
    factorial(5) // 120
    

    七、数组的扩展

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

    扩展运算符后面还可以放置表达式。
    如果扩展运算符后面是一个空数组,则不产生任何效果。

    function push(array, ...items) {
      array.push(...items);
    }
    
    const arr = [
      ...(x > 0 ? ['a'] : []),
      'b',
    ];
    
    [...[], 1]
    // [1]
    

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

    console.log((...[1, 2]))
    // Uncaught SyntaxError: Unexpected number
    

    2、### 替代函数的 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);
    

    2、扩展运算符的应用

    (1)复制数组

    // ES5
    const a1 = [1, 2];
    const a2 = a1.concat();
    // ES6
    const b1 = [...a1]
    const [...b2] = a1
    

    (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' ]
    

    (3)与解构赋值结合
    如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    // ES5
    a = list[0], rest = list.slice(1)
    // ES6
    [a, ...rest] = list
    const [...butLast, last] = [1, 2, 3, 4, 5];
    // 报错
    
    const [first, ...middle, last] = [1, 2, 3, 4, 5];
    // 报错
    

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

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

    (5)实现了 Iterator 接口的对象
    对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

    let nodeList = document.querySelectorAll('div');
    let array = [...nodeList];
    
    
    let arrayLike = {
      '0': 'a',
      '1': 'b',
      '2': 'c',
      length: 3
    };
    
    // TypeError: Cannot spread non-iterable object.
    let arr = [...arrayLike];
    

    (6) Map 和 Set 结构,Generator 函数

    3、Array.from()

    Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

    let arrayLike = {
        '0': 'a',
        '1': 'b',
        '2': 'c',
        length: 3
    };
    
    // ES5的写法
    var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
    
    // ES6的写法
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
    

    对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。

    const toArray = (() =>
      Array.from ? Array.from : obj => [].slice.call(obj)
    )();
    

    Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

    Array.from(arrayLike, x => x * x);
    // 等同于
    Array.from(arrayLike).map(x => x * x);
    

    4、Array.of()

    Array.of方法用于将一组值,转换为数组。

    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    

    Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
    Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。

    Array() // []
    Array(3) // [, , ,]
    Array(3, 11, 8) // [3, 11, 8]
    
    Array.of() // []
    Array.of(undefined) // [undefined]
    Array.of(1) // [1]
    Array.of(1, 2) // [1, 2]
    

    Array.of方法可以用下面的代码模拟实现。

    function ArrayOf(){
      return [].slice.call(arguments);
    }
    

    5、数组实例的 copyWithin()

    数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

    Array.prototype.copyWithin(target, start = 0, end = this.length)
    

    它接受三个参数。
    ----target(必需):从该位置开始替换数据。如果为负值,表示倒数。
    ----start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
    ----end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

    [1, 2, 3, 4, 5].copyWithin(0, 3)
    // [4, 5, 3, 4, 5]
    

    6、数组实例find()和findIndex()

    find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
    findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
    这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

    function f(v){
      return v > this.age;
    }
    let person = {name: 'John', age: 20};
    [10, 12, 26, 15].find(f, person);    // 26
    

    另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

    [NaN].indexOf(NaN)
    // -1
    
    [NaN].findIndex(y => Object.is(NaN, y))
    // 0
    

    7、数组实例fill()

    fill方法使用给定值,填充一个数组。fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。

    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]
    new Array(3).fill(7)
    // [7, 7, 7]
    

    8、数组实例的 entries(),keys() 和 values()

    ES6 提供三个新的方法——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"
    

    如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

    let letter = ['a', 'b', 'c'];
    let entries = letter.entries();
    console.log(entries.next().value); // [0, 'a']
    console.log(entries.next().value); // [1, 'b']
    console.log(entries.next().value); // [2, 'c']
    

    9、数组实例includes()

    Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似

    [1, 2, 3].includes(2)     // true
    [1, 2, 3].includes(4)     // false
    [1, 2, NaN].includes(NaN) // true
    

    该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

    [NaN].indexOf(NaN)
    // -1
    [NaN].includes(NaN)
    // true
    

    10、数组实例flat(),flatMap()

    数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。flat()默认只会“拉平”一层:
    如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
    如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
    如果原数组有空位,flat()方法会跳过空位。

    [1, 2, [3, 4]].flat()
    // [1, 2, 3, 4]
    [1, 2, [3, [4, 5]]].flat()
    // [1, 2, 3, [4, 5]]
    [1, 2, [3, [4, 5]]].flat(2)
    // [1, 2, 3, 4, 5]
    [1, 2, , 4, 5].flat()
    // [1, 2, 4, 5]
    

    flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。flatMap()只能展开一层数组。

    // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
    [2, 3, 4].flatMap((x) => [x, x * 2])
    // [2, 4, 3, 6, 4, 8]
    

    11、数组的空位

    数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
    注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

    Array(3) // [, , ,]
    0 in [undefined, undefined, undefined] // true
    0 in [, , ,] // false
    

    ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
    ----forEach(), filter(), reduce(), every() 和some()都会跳过空位。
    ----map()会跳过空位,但会保留这个值
    ----join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。

    // forEach方法
    [,'a'].forEach((x,i) => console.log(i)); // 1
    // filter方法
    ['a',,'b'].filter(x => true) // ['a','b']
    // every方法
    [,'a'].every(x => x==='a') // true
    // reduce方法
    [1,,2].reduce((x,y) => x+y) // 3
    // some方法
    [,'a'].some(x => x !== 'a') // false
    // map方法
    [,'a'].map(x => 1) // [,1]
    // join方法
    [,'a',undefined,null].join('#') // "#a##"
    // toString方法
    [,'a',undefined,null].toString() // ",a,,"
    

    ES6 则是明确将空位转为undefined。

    Array.from(['a',,'b'])
    // [ "a", undefined, "b" ]
    [...['a',,'b']]
    // [ "a", undefined, "b" ]
    new Array(3).fill('a') // ["a","a","a"]
    let arr = [, ,];
    for (let i of arr) {
      console.log(1);
    }
    // 1
    // 1
    

    八、对象的扩展

    1、属性的简洁表示法

    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    // 等同于
    const baz = {foo: foo};
    
    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!";
      }
    };
    

    2、属性名表达式

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

    // 方法一
    obj.foo = true;
    // 方法二
    obj['a' + 'bc'] = 123;
    /**************************/
    let propKey = 'foo';
    let obj = {
      [propKey]: true,
      ['a' + 'bc']: 123
    };
    /**************************/
    let lastWord = 'last word';
    const a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"
    /**************************/
    let obj = {
      ['h' + 'ello']() {
        return 'hi';
      }
    };
    obj.hello() // hi
    

    3、方法的name属性

    函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

    const person = {
      sayName() {
        console.log('hello!');
      },
    };
    
    person.sayName.name   // "sayName"
    

    4、属性的可枚举性和遍历

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

    let obj = { foo: 123 };
    Object.getOwnPropertyDescriptor(obj, 'foo')
    //  {
    //    value: 123,
    //    writable: true,
    //    enumerable: true,
    //    configurable: true
    //  }
    

    目前,有四个操作会忽略enumerable为false的属性。
    ----for...in循环:只遍历对象自身的和继承的可枚举的属性。
    ----Object.keys():返回对象自身的所有可枚举的属性的键名。
    ----JSON.stringify():只串行化对象自身的可枚举的属性。
    ----Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

    5、属性的遍历

    ES6 一共有 5 种方法可以遍历对象的属性。
    (1)for...in
    for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
    (2)Object.keys(obj)
    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
    (3)Object.getOwnPropertyNames(obj)
    Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
    (4)Object.getOwnPropertySymbols(obj)
    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
    (5)Reflect.ownKeys(obj)
    Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

    6、super关键字

    this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

    const proto = {
      foo: 'hello'
    };
    const obj = {
      foo: 'world',
      find() {
        return super.foo;
      }
    };
    Object.setPrototypeOf(obj, proto);
    obj.find() // "hello"
    

    注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

    // 报错,super用在属性里面,
    const obj = {
      foo: super.foo
    }
    
    // 报错 super用在一个函数里面,然后赋值给foo属性
    const obj = {
      foo: () => super.foo
    }
    
    // 报错 super用在一个函数里面,然后赋值给foo属性
    const obj = {
      foo: function () {
        return super.foo
      }
    }
    

    7、对象的扩展运算符

    数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
    如果扩展运算符后面是一个空对象,则没有任何效果。
    如果扩展运算符后面不是对象,则会自动将其转为对象。
    如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
    对象的扩展运算符等同于使用Object.assign()方法。

    let foo = { ...['a', 'b', 'c'] };
    foo
    // {0: "a", 1: "b", 2: "c"}
    /*******************/
    {...{}, a: 1}
    // { a: 1 }
    /********************/
    // 等同于 {...Object(1)}
    {...1} // {}
    // 等同于 {...Object(true)}
    {...true} // {}
    // 等同于 {...Object(undefined)}
    {...undefined} // {}
    // 等同于 {...Object(null)}
    {...null} // {}
    /************************/
    {...'hello'}
    // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
    /*************************/
    let aClone = { ...a };
    // 等同于
    let aClone = Object.assign({}, a);
    

    8、解构赋值

    let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
    x // 1
    y // 2
    z // { a: 3, b: 4 }
    

    注意:
    解构赋值要求等号右边是一个对象,如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
    解构赋值必须是最后一个参数,否则会报错。
    扩展运算符的解构赋值,不能复制继承自原型对象的属性。

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

    九、对象的新增方法

    1、Object.is()

    ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。

    Object.is('foo', 'foo')
    // true
    Object.is({}, {})
    // false
    +0 === -0 //true
    NaN === NaN // false
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
    

    2、Object.assign()

    Object.assign方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。
    如果只有一个参数,Object.assign会直接返回该参数。
    如果该参数不是对象,则会先转成对象,然后返回。
    如果该参数是undefined和null,就会报错。
    如果非对象参数出现在源对象的位置(即非首参数),首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

    const target = { a: 1 };
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    Object.assign(target, source1, source2);
    
    const obj = {a: 1};
    Object.assign(obj) === obj // true
    typeof Object.assign(2) // "object"
    
    let obj = {a: 1};
    Object.assign(obj, undefined) === obj // true
    Object.assign(obj, null) === obj // true
    
    const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    
    const obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }
    

    注意:
    (1)浅拷贝
    Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
    (2)同名属性的替换
    对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
    (3)数组的处理
    Object.assign可以用来处理数组,但是会把数组视为对象。

    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
    

    (4)取值函数的处理
    Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

    const source = {
      get foo() { return 1 }
    };
    const target = {};
    
    Object.assign(target, source)
    // { foo: 1 }
    

    用途:
    (1)为对象添加属性

    class Point {
      constructor(x, y) {
        Object.assign(this, {x, y});
      }
    }
    

    (2)为对象添加方法

    Object.assign(SomeClass.prototype, {
      someMethod(arg1, arg2) {
        ···
      },
      anotherMethod() {
        ···
      }
    });
    

    (3)克隆对象

    function clone(origin) {
      return Object.assign({}, origin);
    }
    

    (4)合并多个对象

    const merge =
      (...sources) => Object.assign({}, ...sources);
    

    (5)为属性指定默认值

    const DEFAULTS = {
      logLevel: 0,
      outputFormat: 'html'
    };
    
    function processContent(options) {
      options = Object.assign({}, DEFAULTS, options);
      console.log(options);
      // ...
    }
    

    3、Object.getOwnPropertyDescriptors()

    const obj = {
      foo: 123,
      get bar() { return 'abc' }
    };
    
    Object.getOwnPropertyDescriptors(obj)
    // { foo:
    //    { value: 123,
    //      writable: true,
    //      enumerable: true,
    //      configurable: true },
    //   bar:
    //    { get: [Function: get bar],
    //      set: undefined,
    //      enumerable: true,
    //      configurable: true } }
    

    4、proto属性

    5、Object.setPrototypeOf()

    6、Object.setPrototypeOf()

    7、Object.keys()

    ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

    8、Object.values()

    Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

    9、Object.entries()

    Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

    const obj = { foo: 'bar', baz: 42 };
    Object.entries(obj)
    // [ ["foo", "bar"], ["baz", 42] ]
    

    10、Object.fromEntries()

    Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

    Object.fromEntries([
      ['foo', 'bar'],
      ['baz', 42]
    ])
    // { foo: "bar", baz: 42 }
    

    十、Symbol

    1、symbol

    它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
    如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。
    注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
    Symbol 值不能与其他类型的值进行运算,会报错。但是,Symbol 值可以显式转为字符串,Symbol 值也可以转为布尔值,但是不能转为数值。

    let s = Symbol();
    typeof s
    // "symbol"
    let s1 = Symbol('foo');
    s1 // Symbol(foo)
    /******************/
    const obj = {
      toString() {
        return 'abc';
      }
    };
    const sym = Symbol(obj);
    sym // Symbol(abc)
    /******************/
    // 没有参数的情况
    let s1 = Symbol();
    let s2 = Symbol();
    
    s1 === s2 // false
    
    // 有参数的情况
    let s1 = Symbol('foo');
    let s2 = Symbol('foo');
    
    s1 === s2 // false
    /******************/
    let sym = Symbol('My symbol');
    "your symbol is " + sym
    // TypeError: can't convert symbol to string
    `your symbol is ${sym}`
    // TypeError: can't convert symbol to string
    String(sym) // 'Symbol(My symbol)'
    sym.toString() // 'Symbol(My symbol)'
    Boolean(sym) // true
    !sym  // false
    Number(sym) // TypeError
    sym + 2 // TypeError
    

    2、作为属性名的Symbol

    Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

    3、实例:消除魔术字符串

    4、属性名的遍历

    Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

    const obj = {};
    let a = Symbol('a');
    let b = Symbol('b');
    
    obj[a] = 'Hello';
    obj[b] = 'World';
    
    const objectSymbols = Object.getOwnPropertySymbols(obj);
    
    objectSymbols
    // [Symbol(a), Symbol(b)]
    

    5、Symbol.for()

    它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

    let s1 = Symbol.for('foo');
    let s2 = Symbol.for('foo');
    s1 === s2 // true
    

    Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
    Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

    6、Symbol.keyFor()

    Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。

    let s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"
    let s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined
    

    7、Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

    8、内置的 Symbol 值

    (1)Symbol.hasInstance
    (2)Symbol.isConcatSpreadable
    (3)Symbol.species
    (4)Symbol.match
    (5)Symbol.replace
    (6)Symbol.search
    (7)Symbol.split
    (8)Symbol.iterator
    (9)Symbol.toPrimitive
    (10)Symbol.toStringTag
    (11)Symbol.toStringTag

    十一、Set和Map数据结构

    1、Set

    ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
    Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

    const s = new Set();
    [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
    for (let i of s) {
      console.log(i);
    }
    // 2 3 5 4
    /****************************/
    // 例一
    const set = new Set([1, 2, 3, 4, 4]);
    [...set]
    // [1, 2, 3, 4]
    // 例二
    const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    items.size // 5
    /****************************/
    
    

    2、Set 实例的属性和方法

    属性:
    ----Set.prototype.constructor:构造函数,默认就是Set函数。
    ----Set.prototype.size:返回Set实例的成员总数。
    方法:分两种,操作方法(用于操作数据)和遍历方法(用于遍历成员)
    ----add(value):添加某个值,返回 Set 结构本身。
    ----delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    ----has(value):返回一个布尔值,表示该值是否为Set的成员。
    ----clear():清除所有成员,没有返回值。
    Array.from方法可以将 Set 结构转为数组。(这就提供了去除数组重复成员的另一种方法。)

    const items = new Set([1, 2, 3, 4, 5]);
    const array = Array.from(items);
    

    3、Set的遍历操作

    Set 结构的实例有四个遍历方法,可以用于遍历成员。
    ----keys():返回键名的遍历器
    ----values():返回键值的遍历器
    ----entries():返回键值对的遍历器
    ----forEach():使用回调函数遍历每个成员

    4、WeakSet

    WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
    首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
    WeakSet 结构有以下三个方法。
    ----WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
    ----WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
    ----WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
    WeakSet 没有size属性,没有办法遍历它的成员。

    4、Map

    ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

    5、Map实例的属性和操作方法

    (1)size 属性
    (2)set(key, value)
    set方法返回的是当前的Map对象,因此可以采用链式写法。
    (3)get(key)
    (4)has(key)
    (5)delete(key)
    (6)clear()

    5、Map遍历方法

    Map 结构原生提供三个遍历器生成函数和一个遍历方法。
    ----keys():返回键名的遍历器。
    ----values():返回键值的遍历器。
    ----entries():返回所有成员的遍历器。
    ----forEach():遍历 Map 的所有成员。

    6、与其他数据结构相互转换

    (1)Map转为数组
    Map 转为数组最方便的方法,就是使用扩展运算符(...)。

    const myMap = new Map()
            .set(true, 7)
            .set({foo: 3}, ['abc']);
        console.log([...myMap]); // // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
    

    (2)数组转为Map
    将数组传入 Map 构造函数,就可以转为 Map。

    new Map([
      [true, 7],
      [{foo: 3}, ['abc']]
    ])
    // Map {
    //   true => 7,
    //   Object {foo: 3} => ['abc']
    // }
    

    (3)Map 转为对象
    如果所有 Map 的键都是字符串,它可以无损地转为对象。
    如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [k,v] of strMap) {
        obj[k] = v;
      }
      return obj;
    }
    
    const myMap = new Map()
      .set('yes', true)
      .set('no', false);
    strMapToObj(myMap)
    // { yes: true, no: false }
    

    (4)对象转为 Map

    function objToStrMap(obj) {
      let strMap = new Map();
      for (let k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
      }
      return strMap;
    }
    
    objToStrMap({yes: true, no: false})
    // Map {"yes" => true, "no" => false}
    

    (5)Map 转为 JSON
    Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。

    //1
    function strMapToJson(strMap) {
      return JSON.stringify(strMapToObj(strMap));
    }
    
    let myMap = new Map().set('yes', true).set('no', false);
    strMapToJson(myMap)
    // '{"yes":true,"no":false}'
    
    // 2
    function mapToArrayJson(map) {
      return JSON.stringify([...map]);
    }
    
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    mapToArrayJson(myMap)
    // '[[true,7],[{"foo":3},["abc"]]]'
    

    (6)JSON 转为 Map
    JSON 转为 Map,正常情况下,所有键名都是字符串。但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

    //1
    function jsonToStrMap(jsonStr) {
      return objToStrMap(JSON.parse(jsonStr));
    }
    jsonToStrMap('{"yes": true, "no": false}')
    // Map {'yes' => true, 'no' => false}
    //2
    function jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    
    jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
    // Map {true => 7, Object {foo: 3} => ['abc']}
    

    7、WeakMap

    十二、Proxy

    十三、Reflect

    十四、Promise对象

    十五、Iterator和for...of循环

    十六、Generator函数语法

    十七、Generator函数的异步应用

    十八、async函数

    十九、Class基本语法

    1、基本语法

    function Point(x, y) {
      this.x = x;
      this.y = y;
    }
    Point.prototype.toString = function () {
      return '(' + this.x + ', ' + this.y + ')';
    };
    var p = new Point(1, 2);
    // ES6的class改写
    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    typeof Point // "function"
    Point === Point.prototype.constructor // true
    /**---------------------------*/
    class Point {
      constructor() {
        // ...
      }
      toString() {
        // ...
      }
      toValue() {
        // ...
      }
    }
    // 等同于
    Point.prototype = {
      constructor() {},
      toString() {},
      toValue() {},
    };
    

    类的数据类型就是函数,类本身就指向构造函数。
    在类的实例上面调用方法,其实就是调用原型上的方法
    注意:类不存在变量提升,这一点与 ES5 完全不同。

    2、constructor 方法

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

    class Point {
    }
    // 等同于
    class Point {
      constructor() {}
    }
    

    二十、Class的继承

    二十一、Decorator

    二十二、Module语法

    模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

    1、export

    注意:export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

    // 报错
    export 1;
    // 报错
    var m = 1;
    export m;
    // 写法一
    export var m = 1;
    // 写法二
    var m = 1;
    export {m};
    // 写法三
    var n = 1;
    export {n as m};
    
    // 报错
    function f() {}
    export f;
    // 正确
    export function f() {};
    // 正确
    function f() {}
    export {f};
    

    2、import

    import命令具有提升效果,会提升到整个模块的头部,首先执行

    foo();
    import { foo } from 'my_module';
    

    import语句会执行所加载的模块,如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

    import { foo } from 'my_module';
    import { bar } from 'my_module';
    // 等同于
    import { foo, bar } from 'my_module';
    

    3、export default

    export default 命令为模块指定默认输出。因此,一个模块只能有一个默认输出,因此export default命令只能使用一次。
    使用export default时,对应的import语句不需要使用大括号;不使用export default时,对应的import语句需要使用大括号。

    // 第一组
    export default function crc32() { // 输出
      // ...
    }
    
    import crc32 from 'crc32'; // 输入
    
    // 第二组
    export function crc32() { // 输出
      // ...
    };
    
    import {crc32} from 'crc32'; // 输入
    

    本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

    // modules.js
    function add(x, y) {
      return x * y;
    }
    export {add as default};
    // 等同于
    // export default add;
    
    // app.js
    import { default as foo } from 'modules';
    // 等同于
    // import foo from 'modules';
    

    4、export 与 import 的复合写法

    如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

    export { foo, bar } from 'my_module';
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };
    

    export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。

    5、跨模块常量

    设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。

    // constants.js 模块
    export const A = 1;
    export const B = 3;
    export const C = 4;
    // test1.js 模块
    import * as constants from './constants';
    console.log(constants.A); // 1
    console.log(constants.B); // 3
    // test2.js 模块
    import {A, B} from './constants';
    console.log(A); // 1
    console.log(B); // 3
    

    如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

    // constants/db.js
    export const db = {
      url: 'http://my.couchdbserver.local:5984',
      admin_username: 'admin',
      admin_password: 'admin password'
    };
    
    // constants/user.js
    export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
    

    然后,将这些文件输出的常量,合并在index.js里面。

    // constants/index.js
    export {db} from './db';
    export {users} from './users';
    

    使用的时候,直接加载index.js就可以了。

    // script.js
    import {db, users} from './constants/index';
    

    6、import()

    import命令会被 JavaScript 引擎静态分析,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。import()函数,完成动态加载。

    const main = document.querySelector('main');
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });
    

    import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

    二十三、Module的加载实现

    二十四、编程风格

    二十五、读懂风格

    二十六、ArrayBuffer

    。。。。。。

    注:摘抄于ECMAScript 6 入门

    相关文章

      网友评论

          本文标题:ES6笔记

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