ES6 基础扩展
内容简介:
一、ECMAScript简介
二、let和const命令
三、变量的解构赋值
四、字符串的扩展
七、数值的扩展
八,函数的扩展
九、数组的扩展
十、对象的扩展
十一、对象的新增方法
一、ECMAScript简介
1. ECMAScript与JavaScript的关系
1) ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。
2. ES6与ECMAScript2015的关系
1) ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
二、let和const命令
1. let命令
1)基本用法
(1)ES6新增let命令,用于声明变量,作用类似于var,但是let所声明的变量,只在let命令所在的代码块内有效;
(2)在for循环中,使用let命令定义变量时,当前的变量将只在当前的循环中有效,每一轮的循环中的变量都将会是一个新的变量。JavaScript引擎在此时会记住每一次循环的值,从而使得本次的循环会在上一次循环的基础上进行计算。
(3)在for循环中,设置循环变量的部分是一个父作用域,而循环体内部则是一个单独的子作用域;
2)不存在变量提升
(1)var声明的变量存在变量提升,会提升到变量所在环境的开头,即在此环境中,变量在声明之前就可用,只是其值将是undefined;
(2)let声明的变量则必须在声明之后才可以使用,在变量声明之前使用会报错;即let声明的变量只能在声明之后才可以使用;
3)暂时性死区
(1)只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响;
(2)ES6规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡在声明之前使用这些变量,就会报错。这种语法称为“暂时性死区”;
(3)在没有let命令前,typeof操作符是百分百安全的。但是let命令使得在变量声明之前使用typeof操作符会报错(只是在let声明变量前对其使用typeof);
(4)ES6规定暂时性死区以及let和const命令的变量不存在提升,主要是为了减少运行时出现错误,防止在声明之前就是用变量,导致意外的错误发生;
(5)暂时性死区的本质就是:只要已进入当前作用域,所要使用的变量就已经存在了,但是不可获取和使用。只有在声明变量的那行代码出现后,才可以获取和使用该变量;
4)不允许重复声明
(1)let不允许在相同作用域中重复声明同一变量;
(2)函数也是JavaScript中的一种作用域,所以在函数中使用let重复声明参数也是不允许的;
2. 块级作用域
1)块级作用域的意义:在ES5中,只有全局作用域和函数作用域。这带来很多容易出错的地方,一种是意外的内层变量覆盖外层变量,另一种是用来计数的循环变量泄露为全局变量;
2)ES6的块级作用域
(1)let和const为JavaScript新增了块级作用域,“{}”即被视为一个块级作用域;
(2)ES6允许块级作用域的任意嵌套;且每一层都是一个单独的作用域,内层作用域可以读取外层作用域的数据。而外层作用域则无法读取内层作用于的数据;
(3)在多层作用域嵌套时,内层作用域中可以定义与外层作用域中同名的变量,且不会影响外层变量;
(4)块级作用域的出现可以替代匿名立即执行函数表达式;
3)块级作用域与函数声明
(1) ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
(2) ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
(3)实际中,为了防止兼容问题的出现,浏览器在实现该规定时会有自己的行为:
I. 允许在块级作用域中声明函数
II. 此时函数声明类似于var, 即会提升到全局作用域或函数作用域的头部。
III. 同时,函数声明会提升到块级作用域的头部;
IV. 上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
(4) 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
(5) ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
3. const命令
1)基本用法
(1)const命令用于声明一个只读的常量,常量的值一旦声明就不能改变。这意味着,const一旦声明变量,就必须立即初始化,不能留到以后再赋值;
(2)对于const命令,只声明不赋值会报错;
(3) const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
(4) const声明的常量,也与let一样不可重复声明。
2)本质
(1)const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于const声明变量的值为复合数据类型时,const只能保证指针是固定的,不能保证指针所指向的数据结构的变动;
(2)若想保证const声明的复合数据类型也保持固定,则可以使用Object.freeze()方法冻结对象及其属性
3)ES6声明变量的6种方法
(1) ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。
4. 顶层对象的属性
1) 顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
2) ES6规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
5. globalThis对象
1) JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
(1) 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
(2) 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
(3) Node 里面,顶层对象是global,但其他环境都不支持。
2) ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
三、变量的解构赋值
1. 数组的解构赋值
1)基本用法: ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
(1)本质上,数组的解构赋值属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 如果解构不成功,变量的值就等于undefined。
(2)另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
(3)如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。
(4)只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
2)默认值:解构赋值允许指定默认值;
(1)注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值,所以,只有当一个数组成员严格等于undefined,默认值才会生效;
(2) 如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
(3) 默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
2.对象的解构赋值
1)简介
(1)对象的解构赋值与数组不同。数组的解构赋值是按变量所在位置进行的。而对象的属性没有顺序,变量必须与属性同名,才能使用解构赋值取到值;
(2)如果对象的解构赋值失败,变量的值等于undefined;
(3)对象的解构赋值可以很方便的实现将现有对象的方法,赋值到某个变量;
(4) 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let {foo: foo, bar: bar} = {foo: 'aaa', bar: 'bbb'};
(5) 与数组一样,解构也可以用于嵌套结构的对象。 如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
(6) 注意,对象的解构赋值可以取到继承的属性。
2)默认值
(1)对象的解构赋值可以指定默认值。默认值生效的条件是,对象的属性严格等于undefined;
3)注意点
(1) 如果要将一个已经声明的变量用于解构赋值,必须非常小心(即在进行解构赋值时不适用var等关键字了)。 因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
(2) 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
(3) 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
3.字符串的解构赋值
1) 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
2) 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
4.数组和布尔值的解构赋值
1) 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
2) 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
5.函数参数的解构赋值
1)函数的参数也可以使用解构赋值。函数参数也可以使用默认值,但只用值为undefined时才会触发默认值;
6.圆括号的问题
1) 解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
2) 由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
3) 但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。
4)不能使用圆括号的情况
(1)变量声明语句
(2)函数参数
(3)赋值语句的模式
5)可以使用圆括号的情况
(1)赋值语句的非模式部分
7.用途
1)交换变量的值
2)从函数放回多个值:这是因为函数只能放回一个值,若想返回多个值时,就需要把数据放在对象或数组中将其返回。而对象的结构赋值可以很方便的将其取出。
3)函数参数的定义
4)提取JSON数据
5)函数参数的默认值
6)遍历Map结构
7)输入模块的指定方法
四、字符串的扩展
- String.fromCodePoint() ES6 提供,可以识别大于0xFFFF的字符.
- String. raw() ES6 提供,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处 理方法。
- codePointAt() ES6 提供,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
- normalize() ES6 提供,字符串实例的方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规 化。
- includes() 返回布尔值,表示是否找到了参数字符串。
- startsWith() 返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith() 返回布尔值,表示参数字符串是否在原字符串的尾部。
- repeat() 方法返回一个新字符串,表示将原字符串重复n次。
- padStart() ES2017提供,用于头部补全。
- padEnd() ES2017提供,用于尾部补全。
- trimStart() ES2019提供,消除字符串头部的空格。它们返回的都是新字符串,不会修改原始字符串。
- trimEnd() ES2019提供,消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
- matchAll() 方法返回一个正则表达式在当前字符串的所有匹配。
七、数值的扩展
- ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
- Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
- Number.isNaN()用来检查一个值是否为NaN。
- ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
- Number.isInteger()用来判断一个数值是否为整数。
- ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。
- Number.isSafeInteger()则是用来判断一个整数是否落在可表达的范围之内。
- Math对象的扩展
- Math.trunc方法用于去除一个数的小数部分,返回整数部分。
- Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
- Math.cbrt方法用于计算一个数的立方根。
- Math.clz32()方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
- Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
- Math.fround方法返回一个数的32位单精度浮点数形式。
- Math.hypot方法返回所有参数的平方和的平方根。
- 对数相关的四个方法
(1) Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1。
(2) Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN
(3) Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。
(4) Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。 - ES6 新增了 6 个双曲函数方法。
(1)Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
(2)Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
(3)Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
(4)Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
(5)Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
(6)Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent) - ES2016 新增了一个指数运算符(**)。 这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
- ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
八,函数的扩展
1. 函数参数的默认值
1)基本用法 => ES6允许为函数参数设置默认值,即直接写在参数定义的后面
(1) 函数参数可以设置默认值;
(2)参数是默认声明的,不能使用let和const再次声明;
(3)使用参数默认值时,函数不能有同名参数;参数的默认值是惰性求值的;
2)与解构赋值默认值结合使用
(1)通过提供函数默认值可以避免因为没有传递参数而导致的意外报错。
3)参数默认值的位置
(1)设置参数默认值时最好放在参数队尾,这样既能直观的看到哪些参数可省略,也能避免因为不是队尾的参数导致该参数无法省略。
4)函数的length属性
(1)制定了默认参数后,length属性将返回没有设置默认参数的参数个数。即设置了默认参数后,函数的length属性将失真。这是因为length属性含义是该函数预期传入的参数的个数。且若设置了默认参数的参数不是队尾的函数,则length属性将忽略设置了默认参数的参数后的所有参数。
5)作用域
(1)一旦设置了参数的默认值,函数进行声明初始化后,参数会形成一个单独的作用域。在初始 化结束后,该作用域消失,而没有设置默认参数的函数将不存在该情况。
(2)在设置了默认值的参数时,其形成的单独作用域中,参数的赋值变量若不是在该作用域中声明的,则会读取函数外的全局同名变量的值。在此时函数中参数的声明实质是用let声明的参数变量。
(3)应用:利用参数的默认值可以指定某一个参数不得省略,如果省略就抛出一个错误;
2. rest参数
1) ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
2)rest参数是数组形式的存在。而arguments则是类数组对象,其在谁用数组方法时需要通过Array.prototype.slice.call()方法现将arguments转为数组。
3)rest参数只能是最后一个参数,否则会报错;
4)函数的length属性中不包括rest参数;
3. 严格模式
1)ES5中规定了,在函数体内可以使用严格模式
2)ES6中则对该规定做了些修改:只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显示的设定为严格模式,否则会报错;
3)这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行
4)两种方法可以解决该问题:
(1)在全局环境中设置严格模式;
(2)把函数包裹在一个无参数的立即执行函数中;
4. name属性
1)函数的name属性,返回该函数的函数名;
2)在ES6中,匿名函数赋值给变量后,其函数名为该被赋值的变量。而在ES5中则会返回空字符串;
3)Function构造函数返回的函数实例,其name属性的值为anonymous;
4)bind返回的函数,name属性值会加上bound前缀;
5. 箭头函数
1) 基本用法
(1)ES6 允许使用“箭头”(=>)定义函数。
(2)如果箭头函数不需要参数或者需要多个参数时,需要使用“()”将其括起来;
(3)如果箭头函数的代码块部分多余一条语句,则需要使用“{}”,且使用return返回;
(4)箭头函数中的“{}”解释为代码块,若返回一个对象,对象外需添加“{}”;
(5)箭头函数的主要简化功能可以简化回调函数的使用;
2) 使用注意
(1)箭头函数中的this对象就是该函数定义时所在的对象,而不是使用时所在的对象;
(2)不可以用于构造函数(即不能使用为new命令),会报错;
(3)箭头函数中没有arguments对象,可以使用rest参数代替arguments对象;
(4)不可以使用yield命令,因此箭头函数不能用作Generater函数;
(5)在箭头函数中,除了this对象之外,arguments,super,new.target这三个变量在箭头函数中也是不存在的,在箭头函数中使用的这三个变量同样指向外层函数的对应变量;
3)不适用场合 => 这是因为箭头函数是的this对象固定的原因
(1)定义对象的方法,且该方法中包含this对象;因为对象不构成单独的作用域,对象内的箭头函数的this将指向全局环境;
(2)需要动态this的时候;eg:事件代理多个按钮这种情况,箭头函数将无法取得target对象;
(3)若函数体内很复杂,有很多行或者有大量的读写操作,为了可读性这时最好使用普通函数;
4)嵌套的箭头函数
(1)箭头函数内部允许继续使用箭头函数;
(2)可利用箭头函数完成管道机制的布置,即前一个函数的输出是后一个函数的输入;
(3)箭头函数可以很方便的写 λ 演算
6. 尾调用优化
1)尾调用是函数式编程中的重要概念,即某个函数的最后一步是调用另一个函数;
2)尾调用不一定出现在函数的尾部,只要函数的最后一步操作是返回一个函数即可;
3)尾调用优化
(1)函数调用会在内存形成一个“调用记录”,又称“调用帧”,保存调用位置和内部变量的信息;
(2)如果A函数内部包含B函数,则A函数的调用帧会等到B函数调用结束后才会消失,因此而形成“调用栈”;
(3)尾调用因为是函数操作的最后一步,内部函数将不用再保存外部函数的调用帧,因为调用位置和变量信息都不再需要,直接使用内部函数的调用帧取代外层函数的调用帧即可;
(4)尾调用优化即只保存内层函数的调用帧,该方法将大大节省内存;
(5)只有不再用到外层函数的内部变量时,内层函数的调用帧才会取代外层函数的调用帧;
4)尾递归
(1)函数调用自身成为递归,如果尾调用自身则称为尾递归;
(2)递归由于需要保存很多的调用帧,会消耗大量的内存,容易发生“栈溢出”错误。而尾递归则可以再使用递归的同时做到只保存一个调用帧在内存中,大量节省了性能的消耗;
5)递归函数的改写
(1)尾递归的实现,需要改写函数以确保最后一步只调用自身。改写方法就是把所有用到的内部变量改写成函数的参数;
(2)递归本质上是一种循环操作;
6)严格模式
(1)ES6的尾调用优化只在严格模式下开启,在正常模式下是无效的;
(2)因为在正常模式下,函数内部有两个变量可以跟踪函数的调用帧
I. func.arguments:返回调用时函数的参数。
II. func.caller:返回调用当前函数的那个函数。
PS:这是因为尾调用优化时,函数的调用栈会改写,因此上面两个变量会失真。而在严格模式下以上两个变量被禁用。
7)尾递归优化的实现
(1)在正常模式下,可采用“循环”替换掉“递归”的方式实现尾递归;
7. 函数参数的尾逗号
1) ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。而在之前的ES版本中则不允许参数有尾逗号;
8. Function.prototype.toString()
1)ES2019对该方法做出了修改,该方法在此之前会返回忽略了注释和空格的函数代码本身。而修改之后的toString()方法会按照原函数直接返回(即不会忽略注释和空格)
9. catch命令的参数省略
1)ES2019规定:catch语句块的参数可以省略(在不需要捕获该错误的时候)。而在此之前 javascript则不许云省略catch语句块的错误对象这个参数;
九、数组的扩展
1. 扩展运算符
1) 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
2)只有在函数调用时,扩展运算符才可以放在圆括号内,否则会报错;
3)替代函数的apply方法
(1)扩展运算符可以展开数组,所以不再需要apply方法将数组转为参数;
4)扩展运算符的应用
(1)复制数组:由于数组是复合数据类型,直接复制时只是复制了指向内存中该数组位置的指针,而不是克隆一个全新的数组;
(2)合并数组:扩展运算符简化了合并数组的写法,但是该方法属于数组的浅拷贝。
(3)与解构赋值结合:扩展运算符与解构赋值结合用于生成新的数组。而扩展运算符用于数组赋值时只能放在参数的最后一位,否则会报错;
(4)字符串:扩展运算符可以把字符串转为真正的数组。且扩展运算符转换字符串时可以识别四个字节的Unicode字符;
(5)实现了Iterator接口的对象:任何定义了遍历器(Iterator)接口的对象,都可以使用扩展运算符将其转换为真正的数组;而没有部署iterator接口的对象,可以使用Array.from方法将其转换为真正的数组;
(6)Map和Set结构,Generator函数:扩展运算符内部调用的是数据结构的Iterator接口,因此只要具备Iterator接口的对象,都可以使用扩展运算符;
2. Array.from
1)Array.from用于将两类对象转为真正的数组:类似数组的对象(array-like Object即有length属性的对象)和可遍历(iterable)的对象;
2)实际应用中,常见的类数组对象是DOM操作返回的Nodelist集合,以及函数内部的arguments对象;Array.from都可以将它们转换为真正的数组;
3)如果操作的本身就是数组,则Array.from会深复制该数组返回一个新的数组;
4)Array.from可以转化类数组对象和部署了iterator接口的对象。而扩展运算符只能转化部署了 iterator接口的对象,不能转化类数组对象;
5)对于没有实现Array.from方法的浏览器,可以使用Array.prototype.slice()方法替代;
6)Array.from可接受第二个参数(一个函数):作用类似于数组中的map方法,用来对每个元素进行处理,将处理否的值放入返回的数组;
7)Array.from可以接受第三个参数,用来绑定第二个参数中的this值;
8)Array.from可以将各种值转为数组,且提供map功能。这意味着,只要有一个原始的数据结构,就可以先对它的值进行处理,然后转成规范的数组结构,从而使用数量众多的数组方法;
9)Array.from的另一个功能就是将字符串转化为数组,返回字符串的长度。因为它可以正确处理大于正常数值的Unicode字符。
3. Array.of ()
1)该方法用于将一组值转换为数组,该方法的目的是弥补了Array构造函数的不足;因为参数个数的不同,会导致Array()的行为有差异;
2)只有在参数保护少于2个的时候,Array()才会返回由参数组成的新数组。而参数只有一个的时候,实际上是在指定数组的长度,数组的值为undefined;
3)Array.of()可以用来替代Array()和new Array(),因为Array.of()总是返回由参数组成的数组,如果没有参数,就返回空数组;
4. 数组实例的copyWithin()
1) 数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
2)该方法接受3个参数:
(1) target(必需):从该位置开始替换数据。如果为负值,表示倒数。
(2) start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
(3) end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
PS: 以上3个参数都应该是数值,如果不是会先自动转换为数值。
5. 数组实例的find()和findIndex()
1)find():该方法用于找出第一个符合条件的数组成员。参数是一个回调函数,所有数组成员依次执行该函数,直到找出第一个返回true的成员,并将该成员返回,若没有符合条件的,则返回undefined;
2)findIndex():与find方法类似,该方法返回第一个符合条件的数组成员的位置,若都不符合,则返回-1;
3)以上两个方法都可以接受第二个参数,用来绑定回调函数的this对象;
4) indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。
6. 数组实例的fill()
1)fill()方法用于将给定参数去填充一个数组;
2)fill()用于初始化一个数组非常方便,该方法会将原数组的数据全部抹除,用参数进行填充;
3)fill()可接受第二和第三个参数,用来指定填充的起始位置和结束位置(不包括);
4)如果填充的数据类型为对象,则被赋值的将是同一个内存地址的对象,不是深拷贝对象;
7. 数组实例的entries()、keys()和values()
1)以上三个方法都是ES6新增的,用于遍历数组,它们都会返回一个遍历器对象, 可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历;
2) 如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。
8. 数组实例的includes()
1)Array.prototype.includes()方法返回一个布尔值,表示数组是否包含给定的值;
2)该方法接受第二个参数表示搜索的起始位置,默认为0, 如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度,则会重置为从0开始。
9.数组实例的flat()和flapMap()
1)Array.prototype.flap():由于数组的成员可能还是数组,而该方法可以将嵌套的数组成员。“拉平”,变成一个单一的数组,并返回新数组,该方法不修改原始数组;
(1)flap方法默认值拉平一层,可接受一个整数作为参数,表示想要拉平的层数;
(2) 如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数;
(3)如果执行的数组中有空位,flap方法会跳过该空位;
2) flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
(1)flapMap()方法只会拉平一层的数组
(2) flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
(3) flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this。
10. 数组的空位
1) 数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
2) 注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
3)ES5对空位的处理,大多数情况下会忽略空位
(1) forEach(), filter(), reduce(), every() 和some()都会跳过空位。
(2) map()会跳过空位,但会保留这个值。
(3) join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
4)ES6则明确规定将空位转为undefined;
(1) Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
(2) 扩展运算符(...)也会将空位转为undefined。
(3) copyWithin()会连空位一起拷贝。
(4) fill()会将空位视为正常的数组位置。
(5) for...of循环也会遍历空位。
(6) entries()、keys()、values()、find()和findIndex()会将空位处理成undefined。
(7)注意: 由于空位的处理规则非常不统一,所以建议避免出现空位。
11. Array.prototype.sort()的排序稳定性
1) 排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
2) 常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。 不稳定排序的主要缺点是,多重排序时可能会产生问题。
3)ES2019明确规定,Array.prototype.sort()的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。
十、对象的扩展
1. 属性的简洁表示法
1)ES6允许在大括号里直接写入变量和函数,作为对象的属性和方法。
2. 属性名表达式
1)js中定义对象的属性有两种方式:直接用标识符作为属性名。或者用表达式作为属性名,此时表达式要放在“[]”之内;
2)在使用字面量方法定义对象时,ES5只允许使用标识符的方式定义属性。
3)在ES6中,则可以使用标识符方法也可以使用表达式方法在用字面量方法定义对象时定义属性;
4)在ES6中,也可以使用表达式的方式定义对象的方法。属性名表达式和简洁表示法不能同时使用,会报错;
5)若果属性名是一个对象的时候,默认情况下会自动将对象转为字符串[Object Object];
3. 方法的name属性
1)函数的name属性会返回函数的函数名,而对象的方法也是函数,所以也具有name属性;
2)如果对象的方法使用取值函数(getter)和存值函数(setter),则其name属性不在该方法上,而是在该方法的属性的描述对象的get和set属性上,返回值是方法名前加get和set;
3)有两种特殊情况:bind方法创造的函数,其name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous;
4)如果对象属性是一个Symbol值,那么name属性返回的是这个Symbol值得描述;
4. 属性的可枚举性和遍历
1)可枚举性
(1) 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
(2) 描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
(3) 目前,有四个操作会忽略enumerable为false的属性。
I. for...in循环:只遍历对象自身的和继承的可枚举的属性。
II. Object.keys():返回对象自身的所有可枚举的属性的键名。
III. JSON.stringify():只串行化对象自身的可枚举的属性。
IV. Object.assign():ES6新增, 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
(4)ES6规定,所有class的原型和方法都是不可枚举的;
(5)操作中引入继承的属性会使得问题变得复杂,所以尽量使用Object.keys()来替代for...in循环;
2)属性的遍历 => ES6提供了5种方法遍历对象的属性
(1) for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2) Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol属性)的键名。
(3) Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol属性,但是包括不可枚举属性)的键名;
(4) Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5) Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
(6)以上5种方法遍历对象的键名,都遵守同样的属性遍历的次序规则:
I. 首先遍历所有数值键,按照数值升序排列;
II. 其次遍历所有的字符串键,按照加入时间生序排列;
III. 最后遍历所有的symbol键,按照加入时间生序排列;
5. super关键字 => ES6新增,指向当前对象的原型对象
1)注意,在使用super关键字表示对象原型时,只能用在对象的方法之中,用在其他地方会报错;
2)目前,只有在使用对象方法的简写法可以让JavaScript引擎确认,定义的是对象的方法;
3) JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或 Object.getPrototypeOf(this).foo.call(this)(方法)。
6. 对象的扩展运算符 => ES2018将该方法引入了对象中
1)解构赋值
(1) 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
(2)对象的解构赋值要求等号的右边是一个对象,如果等号右边是一个undefined或null,此时就会报错,因为它们无法转换为对象;
(3)解构赋值必须是最后一个参数,否则会报错;
(4)注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合数据类型,那么它拷贝的只是该值的引用指针,而不是这个值的副本;
(5)扩展运算符的解构赋值,不能复制继承自原型对象的属性;
(6)ES6规定,变量声明语句中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式;
2)扩展运算符
(1)对象的扩展运算符用于取出参数对象的所有可比遍历的属性,拷贝到当前对象之中;
(2)由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组中;
(3)如果扩展运算符的后面是一个空对象,则没有任何效果;
(4)如果扩展运算符后面不是对象,则会自动将其转换为对象;
(5)如果扩展运算符后面是字符串,则会将其转换为类数组对象
(6)对象的扩展运算符等同于使用Object.assign()方法;
(7)扩展运算符可以用于合并两个对象;
(8)如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性将被覆盖;
(9)若果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性;
(10)与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式;
7. 链判断运算符
1)ES2020新增,用于简化多层判断求值的过程;
2)链式判断运算符有3中写法:
(1) obj?.prop // 对象属性;
(2) obj?.[expr] // 对象属性;
(3) func?.(...args) // 函数或对象方法的调用;
(4)链式判断的操作意义是:存在即调用该方法或属性,不存在就返回undefined;
(5)一些使用链式判断运算符的常用方法
I. a?.b => a == null ? undefined : a.b;
II. a?.[x] => a == null ? undefined : a[x];
III. a?.b() => a == null ? undefined : a.b();
IV. a?.() => a == null ? undefined : a();
V. 上面的 III 和 IV中的两种情况,若果a.b()和a()不是函数,则会报错
(6)使用链式判断运算符时应注意的几点
I. 短路机制:即链式判断运算符一旦为真,其右侧的表达式将不再求值;
II. delete运算符:如果被判断的元素为undefined或null,则不会进行delete运算;
III. 括号的影响:如果属性链有圆括号,链式判断运算符对括号外部没有影响,只对括号内有影响;
IV. 报错场合:以下情况被禁止,会报错
a. 在构造函数中使用 => new a?.()
b. 链式判断运算符的右侧有模板字符串 => a?.`{b}`
c. 链式判断运算符的左侧是super => super?.()
d. 链式运算符用于赋值运算符的左侧 => a?.b = c
V. 右侧不得为十进制的数值: 为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
8. Null判断运算符
1)读取对象的属性时,如果某个属性的值是null或undefined,有时需要为其制定默认值,而常见的做法是使用 “||”运算符制定默认值;
2)使用“||”判断时,如果属性的值为空字符串,false或0时,此时默认值也会生效,错误赋值;
3)ES2020引入了新的Null判断运算符“??”,其行为类似于“||”,但是只有在运算符左侧的值为null或undefined时,才会返回右侧的值;
4) 这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值;
5) ??有一个运算优先级问题,它与&&和||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
十一、对象的新增方法
1. Object.is()
1) ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
2) ES6 提出“Same-value equality”(同值相等)算法,用来解决在所有环境中,只要两个值一样,它们就应该相等的问题;
3) Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
4)与(==)和(===)不同的地方就是,+0不等于-0,NaN等于自身;
2. Object.assign()
1)基本用法:
(1)Object.assign()方法用于合并对象,将源对象(source)的所有可枚举的属性和方法,复制到目标对象(target)。该方法属于浅拷贝,复制的是指向源对象的指针;
(2)Object.assign()方法的第一个参数是目标对象,后边的参数都是源对象;
(3)注意,如果目标对象与源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性;
(4)如果只有一个参数传入方法,则Object.assign()会直接返回该参数;
(5)如果参数不是对象,Object.assign()方法会先转换为对象,然后返回;
(6)由于undefined和null无法转换为对象,所以它们作为首参数传入时会报错;
(7)如果非对象参数出现在源对象的位置(即非首参数),那么会先将其转换为对象,若无法转化则会跳过。所以若undefined和null作为源对象传入时,不会报错,该值会被跳过;
(8)其他类型的值(即数值、字符串、布尔值)不在首参数时也不会报错。但是,除了字符串会以数组的形式拷贝入目标对象之外,其他值都不会产生效果;这是因为只有字符串的包装对象会产生可枚举属性;
(9)Object.assign()拷贝的属性是有限制的,只会拷贝源对象的自身属性,不拷贝继承属性,也不拷贝不可枚举的属性;而属性名为Symbol的属性也会被拷贝;
2)注意点
(1)浅拷贝:Object.assign()执行的是浅拷贝,目标对象得到的是源对象的引用;
(2)同名属性的替换:Object.assign()一旦遇到同名属性,会执行后写入的对象的属性替换覆盖掉前面对象的同名属性;
(3)数组的处理:Object.assign()可以用来处理数组,但是会把数组视为对象;
(4)取值函数的处理:Object.assign()只能进行值的复制,如果要复制的是一个取值函数,则会求值后再复制;Object.assign()不会复制该函数,只会在取到值后,把值复制过去;
3)常见用途
(1)为对象添加属性 => 可使用Object.assign()方法为对象添加属性;
(2)为对象添加方法 => 可直接使用对象属性的简洁语法,直接将函数放在大括号里,在使用Object.assign()方法把其添加到目标对象中;
(3)克隆对象 => 可将原始对象通过Object.assign()拷贝到一个空对象中完成克隆,此时只能克隆原始对象自身的值,不能克隆原始对象继承的属性和方法;
(4)合并多个对象 => 可将多个对象合并到一起,若想合并返回一个新的对象,则将目标对象设置为空对象即可;
(5)为属性指定默认值 => 利用的是Object.assign()方法的同名属性会被覆盖的特性。
3. Object.getOwnPropertyDescriptors()
1) ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。
2) Object.getOwnPropertyDescriptors()方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
3) 该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
4) Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。
5) Object.getOwnPropertyDescriptors()方法的另一个用处,是配合Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝;
4. _proto_属性,Object.setPrototypeOf(),Object.getPrototypeOf()
1)_proto_属性
(1) 用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
(2)ES6明确规定, 只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
(3) 实现上,__proto__调用的是Object.prototype.__proto__ ;
2)Object.setPrototypeOf()
(1) Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法;
(2)格式:Object.setPrototypeOf(object, prototype);
(3)如果传入方法的一个参数不是对象,会自动将其转换为对象。由于返回值还是第一个参数,所以该操作将不会有任何效果;
(4)由于undefined和null无法转换为对象,所以第一个参数若是undefined和null,则会报错;
3)Object.getPrototypeOf()
(1) 该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。
(2)如果参数不是对象,会自动转换为对象;
(3)如果参数是undefined和null,因其无法转换为对象,所以会报错;
5. Object.keys(),Object.values(),Object.entries()
1)Object.keys()
(1)ES5引入了Object.keys()方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历的属性的键名;
(2) ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。
2)Object.values()
(1)Object.values()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历的属性的键值;
(2)Object.values()方法会过滤掉属性名为symbol值得属性;
(3)如果Object.values()的参数是一个字符串,则会返回各个字符组成的一个数组;因为字符串会先转换为类似数组的对象,字符串的每个字节,就是该对象的一个属性。
(4) 如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。
3)Object,entries()
(1)Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历的属性的键值对数组;
(2)如果原对象的属性名是一个symbol值,则该属性会被忽略;
(3)Object.entries()的基本用途是遍历对象的属性;
(4)Object.entries()的另一个用途是,将对象转为真正的Map结构;
6.Object.fromEntries()
(1) Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
(2) 该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
(3) 该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。