美文网首页ES6入门摘入
ES6入门摘录笔记(二)

ES6入门摘录笔记(二)

作者: eastbaby | 来源:发表于2017-10-12 21:17 被阅读435次

    三,字符串扩展

    3.1 Unicode表示法

    ES6 做出了改进,只要将码点放入大括号,就能正确解读该字符。
    有了这种表示法之后,JavaScript 共有6种方法可以表示一个字符。

    '\z' === 'z'  // true
    '\172' === 'z' // true
    '\x7A' === 'z' // true
    '\u007A' === 'z' // true
    '\u{7A}' === 'z' // true
    

    3.2 codePointAt()

    这篇文章讲的特别好!!解释了什么事unicode,utf-8,utf-16以及javascript的实现。

    对于四字节字符,charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的码点。

    let s = '𠮷a';
    s.codePointAt(0) // 134071
    

    codePointAt方法的参数,仍然是不正确的。比如,上面代码中,字符a在字符串s的正确位置序号应该是1,但是必须向codePointAt方法传入2。解决这个问题的一个办法是使用for...of循环,因为它会正确识别32位的UTF-16字符。

    codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

    function is32Bit(c) {
      return c.codePointAt(0) > 0xFFFF;
    }
    

    3.3 String.fromCodePoint()

    ES6提供了String.fromCodePoint方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。

    String.fromCodePoint(0x20BB7)
    // "𠮷"
    String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
    // true
    

    上面代码中,如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。

    3.4 字符串遍历接口

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

    3.5 at()

    目前,有一个提案,提出字符串实例的at方法,可以识别 Unicode 编号大于0xFFFF的字符,返回正确的字符。ES5的charAt方法有局限。这个方法可以通过垫片库实现。

    3.6 normalize()

    ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。

    '\u01D1'.normalize() === '\u004F\u030C'.normalize()
    // true
    

    3.7 includes(), startsWith(), endsWith()

    传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

    let s = 'Hello world!';
    s.startsWith('world', 6) // true
    s.endsWith('Hello', 5) // true
    s.includes('Hello', 6) // false
    

    上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

    3.8 repeat()

    repeat方法返回一个新字符串,表示将原字符串重复n次。

    'x'.repeat(3) // "xxx"

    3.9 padStart(),padEnd()

    'x'.padStart(4, 'ab') // 'abax'
    'x'.padEnd(5, 'ab') // 'xabab'
    

    上面代码中,padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的长度,第二个参数是用来补全的字符串。

    3.10 模板字符串(template string)

    模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

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

    模板字符串甚至还能嵌套。

    3.11 实例:模板编译

    这里是模板字符串的一个例子。
    通过模板字符串,生成正式模板的实例。具体代码看教程。

    3.12 标签模板(tagged template)

    模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”。标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

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

    “标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。
    标签模板的另一个应用,就是多语言转换(国际化处理)。模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。

    模板处理函数的第一个参数(模板字符串数组),还有一个raw属性。

    3.13 string.raw

    ES6还为原生的String对象,提供了一个raw方法。

    String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。

    String.raw`Hi\n${2+3}!`;
    // "Hi\\n5!"
    

    3.14 模板字符串的限制

    前面提到标签模板里面,可以内嵌其他语言。但是,模板字符串默认会将字符串转义,导致无法嵌入其他语言。为了解决这个问题,现在有一个提案,放松对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串。注意,这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错。

    四,正则的扩展

    4.1 RegExp 构造函数

    在 ES5 中,RegExp构造函数的参数有两种情况。第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag); 第二种情况是,参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝,但不允许此时使用第二个参数添加修饰符,否则会报错。

    ES6 改变了。如果RegExp第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

    4.2 字符串的正则方法

    4.3 u 修饰符

    ES6 对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的 Unicode 字符。一旦加上u修饰符号,就会修改下面这些正则表达式的行为。

    var s = '𠮷';
    
    /^.$/.test(s) // false
    /^.$/u.test(s) // true
    
    /\u{61}/.test('a') // false
    /\u{61}/u.test('a') // true
    
    /𠮷{2}/.test('𠮷𠮷') // false
    /𠮷{2}/u.test('𠮷𠮷') // true
    

    利用这一点,可以写出一个正确返回字符串长度的函数。

    function codePointLength(text) {
      var result = text.match(/[\s\S]/gu);
      return result ? result.length : 0;
    }
    

    还可以用spread判断:

    function length(str) {
      return [...str].length;
    }
    

    4.4 y 修饰符

    ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。

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

    y修饰符的设计本意,就是让头部匹配的标志^在全局匹配中都有效。

    4.5 sticky 属性

    与y修饰符相匹配,ES6 的正则对象多了sticky属性,表示是否设置了y修饰符。

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

    4.6 flags 属性

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

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

    4.7 s 修饰符

    点(.)是一个特殊字符,代表任意的单个字符,但是行终止符(line terminator character)除外。但是,很多时候我们希望匹配的是任意单个字符,这时有一种变通的写法。/foo[^]bar/.test('foo\nbar') // true
    这种解决方案毕竟不太符合直觉,所以现在有一个提案,引入/s修饰符,使得.可以匹配任意单个字符。/foo.bar/s.test('foo\nbar') // true

    这被称为dotAll模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。

    4.8 后行断言

    JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个提案,引入后行断言,V8 引擎4.9版已经支持。

    4.9 Unicode 属性类

    目前,有一个提案,引入了一种新的类的写法\p{...}
    \P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。

    4.10 具名组匹配

    正则表达式使用圆括号进行组匹配。const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
    上面代码中,正则表达式里面有三组圆括号。使用exec方法,就可以将这三组匹配结果提取出来。

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

    组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号引用,要是组的顺序变了,引用的时候就必须修改序号。
    现在有一个“具名组匹配”(Named Capture Groups)的提案,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

    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
    

    如果具名组没有匹配,那么对应的groups对象属性会是undefined。

    解构赋值和替换
    有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。

    引用
    如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法。数字引用(\1)依然有效。

    五,数值的扩展

    5.1 二进制和八进制表示法

    ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。从 ES5 开始,在严格模式之中,八进制就不再允许使用前缀0表示,ES6 进一步明确,要使用前缀0o表示。ES5中没有二进制的定义。

    5.2 Number.isFinite(), Number.isNaN()

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

    5.3 Number.parseInt(), Number.parseFloat()

    ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

    5.4 Number.isInteger()

    Number.isInteger()用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。

    5.5 Number.EPSILON

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

    Number.EPSILON可以用来设置“能够接受的误差范围”。比如,误差范围设为2的-50次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。

    5.6 安全整数和 Number.isSafeInteger()

    JavaScript 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值。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()则是用来判断一个整数是否落在这个范围之内。实际使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。

    5.7 Math对象的扩展

    Math.trunc() Math.sign() Math.cbrt() Math.clz32() Math.imul() Math.fround() Math.hypot()
    对数方法(1) Math.expm1()(2)Math.log1p()(3)Math.log10()(4)Math.log2()
    ES6新增了6个双曲函数方法。Math.sinh(x)等

    5.8 Math.signbit()

    目前,有一个提案,引入了Math.signbit() 方法判断一个数的符号位是否设置了。

    5.9 指数运算符

    ES2016 新增了一个指数运算符()。指数运算符可以与等号结合,形成一个新的赋值运算符(=)。注意,在 V8 引擎中,指数运算符与Math.pow的实现不相同,对于特别大的运算结果,两者会有细微的差异。

    5.10 Integer 数据类型

    JavaScript 所有数字都保存成64位浮点数,这决定了整数的精确程度只能到53个二进制位。大于这个范围的整数,JavaScript 是无法精确表示的。
    现在有一个提案,引入了新的数据类型 Integer(整数),来解决这个问题。整数类型的数据只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

    1n + 2n // 3n
    0b1101n // 二进制
    0o777n // 八进制
    0xFFn // 十六进制
    

    Integer 类型不能与 Number 类型进行混合运算。相等运算符(==)会改变数据类型,也是不允许混合使用。精确相等运算符(===)不会改变数据类型,因此可以混合使用。

    几乎所有的 Number 运算符都可以用在 Integer,但是有两个除外:不带符号的右移位运算符>>>和一元的求正运算符+,使用时会报错。

    六,函数的扩展

    6.1 函数参数的默认值

    ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。为了避免参数y赋值了但是对应的布尔值为false的问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

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

    ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。function Point(x = 0, y = 0)。 参数变量是默认声明的,所以不能用let或const再次声明。

    使用参数默认值时,函数不能有同名参数。

    // 不报错
    function foo(x, x, y) {
      // ...
    }
    // 报错
    function foo(x, x, y = 1) {
      // ...
    }
    

    与解构赋值默认值结合使用

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

    只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

    参数默认值的位置
    通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的,除非显式输入undefined。如果传入undefined,将触发该参数等于默认值,null则没有这个效果。

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

    这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

    作用域

    一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

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

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

    例子2,

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

    如果此时,全局变量x不存在,就会报错。

    应用

    function throwIfMissing() {
      throw new Error('Missing parameter');
    }
    function foo(mustBeProvided = throwIfMissing()) {
      return mustBeProvided;
    }
    foo()
    // Error: Missing parameter
    

    利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。注意函数名throwIfMissing之后有一对圆括号,这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。

    另外一个应用,可以将参数默认值设为undefined,表明这个参数是可以省略的。

    6.2 rest 参数

    ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

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

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

    6.3 严格模式

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

    这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。

    两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。第二种是把函数包在一个无参数的立即执行函数里面。

    6.4 name 属性

    函数的name属性,返回该函数的函数名。这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准。

    需要注意的是,ES6 对这个属性的行为做出了一些修改。传入匿名函数的时候:

    var f = function () {};
    
    // ES5
    f.name // ""
    
    // ES6
    f.name // "f"
    

    如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
    Function构造函数返回的函数实例,name属性的值为anonymous。bind返回的函数,name属性值会加上bound前缀。

    6.5 箭头函数

    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。var sum = (num1, num2) => { return num1 + num2; }
    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号(),否则会报错。

    如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。let fn = () => void doesNotReturn();

    箭头函数可以与变量解构结合使用。

    const full = ({ first, last }) => first + ' ' + last;
    
    // 等同于
    function full(person) {
      return person.first + ' ' + person.last;
    }
    

    箭头函数有几个使用注意点。

    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。同理super、new.target不能用。

    (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

    this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

    (this部分这里参见教程的代码例子,很丰富。)

    6.6 绑定 this

    ES7提出了“函数绑定”(function bind)运算符,用来取代call 、apply、bind
    调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。

    函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

    6.7 尾调用优化

    尾调用(Tail Call) 是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

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

    我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。所有的调用帧,就形成一个“调用栈”(call stack)。

    尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。这就叫做“尾调用优化”(Tail call optimization)。注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

    函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。

    递归函数的改写。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来。两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。第二种方法就简单多了,就是采用 ES6 的函数默认值。
    总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。

    ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
    关于尾递归优化的实现看教程代码,有点小复杂。

    6.8 函数参数的尾逗号

    ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
    此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
    这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。但是 JSON 不支持尾后逗号。

    6.9 catch 语句的参数

    目前,有一个提案,允许try...catch结构中的catch语句调用时不带有参数。这个提案跟参数有关,也放在这一章介绍。

    七,数组的扩展

    7.1 扩展运算符(spread)

    ...是扩展运算符。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。Rest操作符一般用在函数参数的声明中,而Spread用在函数的调用中。

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

    扩展运算符的应用
    (1)复制数组
    数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
    ES5 只能用变通方法来复制数组。

    const a1 = [1, 2];
    const a2 = a1.concat();
    a2[0] = 2;
    a1 // [1, 2]
    

    扩展运算符提供了复制数组的简便写法。

    const a1 = [1, 2];
    // 写法一
    const a2 = [...a1];
    // 写法二
    const [...a2] = a1;
    

    (2)合并数组

    扩展运算符提供了数组合并的新写法。

    // ES5的合并数组
    arr1.concat(arr2, arr3);
    // ES6的合并数组
    [...arr1, ...arr2, ...arr3]
    

    (3)与解构赋值结合

    // ES5
    a = list[0], rest = list.slice(1)
    // ES6
    [a, ...rest] = list
    

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

    (4)字符串
    有一个重要的好处,那就是能够正确识别四个字节的 Unicode 字符。

    (5)实现了 Iterator 接口的对象
    任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。如let array = [...nodeList];。对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

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

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

    实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

    值得提醒的是,扩展运算符(...)也可以将某些数据结构转为数组。扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。

    Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

    // arguments对象
    function foo() {
      const args = [...arguments];
    }
    
    // NodeList对象
    [...document.querySelectorAll('div')]
    

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

    Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。

    7.3 Array.of()

    Array.of方法用于将一组值,转换为数组。Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。
    这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。只有当参数个数不少于2个时,Array()才会返回由参数组成的新数组。

    Array() // []
    Array(3) // [, , ,]
    Array(3, 11, 8) // [3, 11, 8]
    

    7.4 数组实例的 copyWithin()

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

    7.5 数组实例的 find() 和 findIndex()

    [1, 4, -5, 10].find((n) => n < 0)
    // -5
    [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    }) // 10
    

    数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。indexof它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。

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

    7.6 数组实例的fill()

    fill方法使用给定值,填充一个数组。fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

    ['a', 'b', 'c'].fill(7, 1, 2)
    // ['a', 7, 'c']
    

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

    它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历。如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

    7.8 数组实例的 includes()

    与字符串的includes方法类似,参数负的情况稍有不同。

    7.9 数组的空位

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

    ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
    forEach(), filter(), every() 和some()都会跳过空位。
    map()会跳过空位,但会保留这个值
    join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
    ES6 则是明确将空位转为undefined。
    由于空位的处理规则非常不统一,所以建议避免出现空位。

    八,对象的扩展

    8.1 属性的简洁表示法

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

    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    
    function f(x, y) {
      return {x, y};
    }
    
    // 等同于
    
    function f(x, y) {
      return {x: x, y: y};
    }
    

    除了属性简写,方法也可以简写。

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

    如果某个方法的值是一个 Generator 函数,前面需要加上星号。

    8.2 属性名表达式

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

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

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

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

    表达式还可以用于定义方法名。注意,属性名表达式与简洁表示法,不能同时使用,会报错。

    注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。

    8.3 方法的 name 属性

    函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。

    const obj = {
      get foo() {},
      set foo(x) {}
    };
    
    obj.foo.name
    // TypeError: Cannot read property 'name' of undefined
    
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
    
    descriptor.get.name // "get foo"
    descriptor.set.name // "set foo"
    

    有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。

    8.4 Object.is()

    ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
    ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。

    8.5 Object.assign()

    Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。属性名为 Symbol 值的属性,也会被Object.assign拷贝。

    注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
    注意点
    (1)浅拷贝(2)同名属性的替换(3)数组处理时视为对象 (4)取值函数的处理将求值后再复制。

    应用:(1)为对象添加属性(2)为对象添加方法(3)克隆对象(4)合并多个对象(5)为属性指定默认值

    8.6 属性的可枚举性和遍历

    可枚举性
    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。ES5中 Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性。

    目前,有四个操作会忽略enumerable为false的属性。

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

    另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。

    属性的遍历
    ES6 一共有5种方法可以遍历对象的属性。

    (1)for...in
    (2)Object.keys(obj
    (3)Object.getOwnPropertyNames(obj)(4)Object.getOwnPropertySymbols(obj)
    (5)Reflect.ownKeys(obj)

    以上的5种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

    首先遍历所有数值键,按照数值升序排列。
    其次遍历所有字符串键,按照加入时间升序排列。
    最后遍历所有 Symbol 键,按照加入时间升序排列。

    8.7 Object.getOwnPropertyDescriptors()

    ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。

    该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。这是因为Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。这时,Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以实现正确拷贝。

    Object.getOwnPropertyDescriptors方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。

    另外,Object.getOwnPropertyDescriptors方法可以实现一个对象继承另一个对象。

    8.8 _proto_属性, Object.setPrototypeOf(), Object.getPrototypeOf()

    JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法。

    proto属性 该属性没有写入 ES6 的正文,而是写入了附录,原因是proto前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

    Object.setPrototypeOf() Object.setPrototypeOf方法的作用与proto相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

    Object.getPrototypeOf() 该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

    8.9 super 关键字

    我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

    const obj = {
      find() {
        return super.foo;
      }
    };
    

    JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。

    8.10 Object.keys(),Object.values(),Object.entries()

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

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

    Object.entries的基本用途是遍历对象的属性。Object.entries方法的另一个用处是,将对象转为真正的Map结构。

    8.11 对象的扩展运算符(spread)

    ES2017 将这个运算符引入了对象。
    (1)解构赋值

    单纯的解构赋值,所以可以读取对象继承的属性;扩展运算符的解构赋值,只能读取对象o自身的属性。

    (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);
    

    扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

    8.12 Null 传导运算符

    编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName
    ,安全的写法是写成下面这样。const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';

    这样的层层判断非常麻烦,因此现在有一个提案,引入了“Null 传导运算符”(null propagation operator)?.
    ,简化上面的写法。const firstName = message?.body?.user?.firstName || 'default';

    上面代码有三个?.运算符,只要其中一个返回null或undefined,就不再往下运算,而是返回undefined。

    相关文章

      网友评论

        本文标题:ES6入门摘录笔记(二)

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