ES6—ES13新语法解析

ES6—ES13语法总览


关系:
ECMA-Script 是 JavaScript 的标准与规范,JavaScript 是 ECMA-Script 标准的实现和扩展。

e101b4e661916f085eb8f69393fc8a80.png

一、ES6 新特性(2015)

1. let和const

特点比较:

特性varletconst
变量提升✔️××
全局变量✔️××
重复声明✔️××
重新赋值✔️✔️×
暂时性死区×✔️✔️
块作用域×✔️✔️
只声明不初始化✔️✔️×

其中的四点:

(1)重新赋值

const 是指针不可修改,并非数据不能修改,

const a = 1;
a = 2;// 报错,基本类型不可修改

const b = { c: 1, d: 2 }
b.c = 'c';//不会报错,因为 b 指向的对象指针没有改变,只是里面的属性或属性值发生变化
b = { ...};// 报错
(2)块级作用域

非函数级同名变量会被覆盖,如下:

var a = 1;
if (true) { var a = 2; }
console.log(a); // 输出结果:2

循环变量会泄漏为全局变量(如果是声明函数里面泄漏为函数级变量):

var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 输出结果:1  2  3
}
console.log(i); // 输出结果:3

而通过let和const定义的变量存在块级作用域,就不会产生上述问题:

let a = 1;
if (true) { let a = 2; }
console.log(a); // 输出结果:1
 
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 输出结果:1  2  3
}
console.log(i); // Uncaught ReferenceError: i is not defined

var 是以函数级为作用域,let、const以大括号{ } 为作用域,类似for循环除外

(3)变量提升

变量提升:就是变量可以在声明之前使用:

console.log(a); // 输出结果:undefined
var a = 1;

// 实际转化为 
var a; // 声明提升,初始值 undefined,赋值是另一个操作
console.log(a); // 输出结果:undefined
a = 1;

let 和 const 限制了变量提升,在变量创建到真正初始化之间的时间跨度内,它们无法访问或使用,ES6 将其称之为暂时性死区:

// 暂时性死区 开始
a = "hello";     //  Uncaught ReferenceError: Cannot access 'a' before initialization
 
let a;   
//  暂时性死区 结束
console.log(a);  // undefined
(4)重复声明

var 没有限制

function funcA() {
  var a = 1;
  var a = 2;
}
 
function funcB(args) {
  var args = 1; 
}

而let修复了这种不严谨的设计:

function funcA() {
  let a = 1;
  let a = 2;  // Uncaught SyntaxError: Identifier 'a' has already been declared
}
 
function funcB(args) {
  let args = 1;  // Uncaught SyntaxError: Identifier 'args' has already been declared
}

现在我们项目中已经完全放弃了var,而使用 const 来定义变量,极少部分需要改动的变量使用 let 。可以在ESlint开启如下规则:

"no-var": 0;

2. 解构赋值

(1)数组解构

具有 Iterator 接口的数据结构,都可以采用数组形式的解构赋值。位置与变量对应

const [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz) // 输出结果:1  2  3

不完全解构,只解构部分内容:没有值就会将变量赋值为undefined

const [x, y] = [1, 2, 3];   // 提取前两个值
const [, y, z] = [1, 2, 3]  // 提取后两个值
const [x, , z] = [1, 2, 3]  // 提取第一三个值
const [x, y, z] = [1, 2];   // z 为 undefined

rest 操作符来捕获剩余项:

const [x, ...y] = [1, 2, 3];   // x 为 1,y 为 [2, 3]

解构时支持使用默认值,当对应的值为 undefined 时会使用默认值:

const [x, y, z = 3] = [1, 2]; 
console.log(z)  // 输出结果:3
(2)对象解构

对象解构的本质是先找到同名属性(因为对象没有顺序),在赋值给对应变量:

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar); // 输出结果:aaa  bbb

// 本质 (一一对应,所以foo为 'aaa')
let { foo:foo, bar:bar } = { foo: 'aaa', bar: 'bbb' };

// 由此,可以设置别名,如下:too 就是 foo 的别名,
let { foo:too, bar:bar } = { foo: 'aaa', bar: 'bbb' };
console.log(too); // 输出结果:aaa

对象的解构赋值也是支持默认值的,当定义的变量在对象中不存在(严格的===undefined)时,其默认值才会生效:

let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb', baz: null };
console.log(foo, bar, baz); // 输出结果:aaa  bbb  null
 
let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar, baz); // 输出结果:aaa  bbb  ccc

注意:不能给已声明的变量进行赋值,因为当缺少 let、const、var 关键字时,将会把 {baz} 理解为代码块从而导致语法错误,所以下面代码会报错:

let baz;
{ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' };

可以使用括号包裹整个解构赋值语句来解决上述问题:

let baz;
({ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' });
console.log(baz)

在对象的解构赋值中,可以将现有对象的方法赋值给某个变量,比如:

let { log, sin, cos } = Math;
log(12)  // 输出结果:2.4849066497880004
sin(1)   // 输出结果:0.8414709848078965
cos(1)   // 输出结果:0.5403023058681398
(3)其他解构赋值
  • 字符串解构

字符串解构规则:只要等号右边的值不是对象或数组,就先将其转为类数组对象,在进行解构:

const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e)  // 输出结果:h e l l o

类数组对象有 length 属性,因此可以给这个属性进行解构赋值:

let {length} = 'hello';    // 输出结果:5
  • 数值和布尔值解构赋值

对数值和布尔值进行解构时,会先被转为对象,然后再应用解构语法:

let {toString: s} = 123;
s === Number.prototype.toString // 输出结果:true
 
let {toString: s} = true;
s === Boolean.prototype.toString // 输出结果:true

注意:null 和 undefined 不能转换为对象,所以如果右边是这两个值,就会报错。

  • 函数参数解构赋值

函数参数表面上是一个数组,在传入参数的那一刻,就会被解构为x和y。

function add([x, y]){
  return x + y;
}
add([1, 2]);   // 3

解构函数的返回值:

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

3. 模板字符串

模板字符串:增强版的字符串,用反引号``来标识,可以用来定义单行、多行字符串,或者在字符串中嵌入变量。解决字符串拼接时的繁琐

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
 
// 字符串中调用函数
` ${fn()}`

在使用模板字符串时,需要注意以下几点:

  • 如果在字符串中使用反引号,需要使用\来转义;
  • 如果在多行字符串中有空格和缩进,那么它们都会被保留在输出中;
  • 模板字符串中嵌入变量,需要将变量名写在${}之中;
  • 模板字符串中可以放任意的表达式、运算、引用属性、调用函数;
  • 如果模板字符中的变量没有声明,会报错。

4. 函数默认参数

ES6之前,函数不支持默认参数,ES6支持默认参数,并且只有不传入参数或 明确传入 undefined 时才会触发默认值:

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

当使用函数默认值时,需要注意以下几点:

(1)函数length属性值

此时 length 表示的就是第一个有默认值参数之前的普通参数个数:

const funcA = function(x, y) {};
console.log(funcA.length);  // 输出结果:2 
 
const funcB = function(x, y = 1) {};
console.log(funcB.length);  // 输出结果:1
 
const funcC = function(x = 1, y) {};
console.log(funcC.length);  // 输出结果 0
(2)参数作用域

当给函数的参数设置了默认值之后,参数在被初始化时将形成一个独立作用域,初始化完成后作用域消解:

let x = 1;
 
function func(x, y = x) { // y 为传入时的 x 值
  console.log(y);
}
 
func(2);// 2

在函数调用时,参数 x, y 将形成一个独立的作用域,所以参数中的y会等于第一个参数中的x,而不是上面定义的1。

5. 箭头函数

用来简化函数的定义:

const counter = x => x*x;

箭头函数特点:

(1)更加简洁
(2)不绑定 this
(3)不可作为构造函数
(4)不绑定 arguments

6. 扩展运算符

扩展运算符:… 就是rest参数的逆运算

spread 扩展运算符有以下用途

(1)将数组转化为用逗号分隔的参数序列:
var arr = [1, 2, 3];
test(…arr); // 函数传参


(2)将一个数组拼接到另一个数组:

```go
var arr1 = [1, 2, 3,4];
var arr2 = [...arr1, 4, 5, 6];// [1, 2, 3, 4, 4, 5, 6]

(3)将字符串转为逗号分隔的数组:

var str='JavaScript';
var arr= [...str];// ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

7. Symbol

ES6基本数据类型:Symbol,表示独一无二的值。它是一种类似于字符串的数据类型
特点:

  • Symbol的值是唯一的,用来解决命名冲突的问题
  • Symbol值不能与其他类型数据进行运算
  • Symbol定义的对象属性不能使用for...in遍历循环,但是可以使用Reflect.ownKeys 来获取对象的所有键名
let s1 = Symbol();
console.log(typeof s1); // "symbol"
 
let s2 = Symbol('hello');
let s3 = Symbol('hello');
console.log(s2 === s3); // false

基于以上特性,Symbol 属性类型比较适合用于两类场景中:常量值和对象属性

(1)避免常量值重复

getValue 函数会根据传入字符串参数 key 执行对应代码逻辑:

function getValue(key) {
  switch(key){
    case 'A':
    case 'B':
  }
}

魔术字符串:如上,指在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值,导致调用 getValue 函数时需要查看函数代码才能找到参数 key 的可选值。

优化:将参数 key 的值以常量的方式声明

const KEY = {
  alibaba: 'A',
  baidu: 'B',
}
function getValue(key) {
  switch(key){
    case KEY.alibaba:
    case KEY.baidu:
  }
}
getValue(KEY.baidu);

但这样也并非完美,假设现在要在 KEY 常量中加入一个 key,根据对应的规则,很有可能会出现值重复的情况:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
  tencent: 'B'
}
这就会出现问题:
getValue(KEY.baidu) // 等同于 getValue(KEY.tencent)

所以在这种场景下更适合使用 Symbol,不需要关心值本身,只关心值的唯一性

优化:

const KEY = {
  alibaba: Symbol(),
  baidu: Symbol(),
  tencent: Symbol()
}
Symbol()唯一性
getValue(KEY.baidu) // 将不等同 getValue(KEY.tencent)
(2)避免对象属性覆盖

函数 fn 需要对传入的对象参数添加一个临时属性 user,但可能该对象参数中已经有这个属性了,如果直接赋值就会覆盖之前的值。此时就可以使用 Symbol 来避免这个问题。创建一个 Symbol 数据类型的变量,然后将该变量作为对象参数的属性进行赋值和读取,这样就能避免覆盖的情况:

function fn(o) { // {user: {id: xx, name: yy}}
  const s = Symbol()
  o[s] = 'zzz'
}

8. 集合 Set

ES6 Set 集合:类似于数组,成员值都是唯一的,集合实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。

Set的属性和方法:

属性和方法概述
size返回集合的元素个数
add增加一个新的元素,返回当前的集合
delete删除元素,返回布尔值
has检查集合中是否包含某元素,返回布尔值
clear清空集合,返回undefined
创建空集合
let s = new Set();

创建非空集合
let s1 = new Set([1,2,3,1,2,3]); // 会去重

返回集合元素个数
console.log(s1.size);       // 3

添加新元素
console.log(s1.add(4));     // {1,2,3,4}

删除元素
console.log(s1.delete(1));  //true

检测是否存在某个值
console.log(s1.has(2));     // true

清空集合
console.log(s1.clear());    //undefined

数组去重
let arr = [1,2,3,2,1]
Array.from(new Set(arr))  // {1, 2, 3}

可以通过set来求两个数组的交集和并集:

// 模拟求交集 
let intersection = new Set([...set1].filter(x => set2.has(x)));
 
// 模拟求差集
let difference = new Set([...set1].filter(x => !set2.has(x)));

用以下方法可以进行数组与集合的相互转化:

// Set集合转化为数组
const arr = [...mySet]
const arr = Array.from(mySet)
 
// 数组转化为Set集合
const mySet = new Set(arr)

9. Map

ES6 Map数据结构:类似于对象,但它的键值范围不限于字符串,可以是任何类型, 对象是“ 字符串—值”, Map 结构是“ 值—值” 的对应, 是一种更完善的 Hash 结构实现。Map也实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。

Map的属性和方法:

属性和方法概述
size返回Map的元素个数
set增加一个新的元素,返回当前的Map
get返回键名对象的键值
has检查Map中是否包含某元素,返回布尔值
clear清空Map,返回undefined
创建一个空 map
let m = new Map();

创建一个非空 map,传递二维数组,每一个二维数组对应键和值
let m2 = new Map([
 ['name', 'hello'],
]);

获取映射元素的个数
console.log(m2.size);          // 1

添加映射值
console.log(m2.set('age', 6)); // {"name" => "hello", "age" => 6}

获取映射值
console.log(m2.get('age'));    // 6

检测是否有该映射
console.log(m2.has('age'));    // true

清除
console.log(m2.clear());       // undefined

需要注意, 只有对同一个对象的引用(指针), Map 结构才将其视为同一个键:

let map = new Map(); 
map.set(['a'], 555); 
map.get(['a']) // undefined

上面代码的set和get方法, 表面是针对同一个键, 但实际上这是两个值, 内存地址是不一样的, 因此get方法无法读取该键, 所以会返回undefined。

如果 Map 的键是一个简单类型的值( 数字、 字符串、 布尔值), 则只要两个值严格相等, Map 将其视为一个键, 包括0和 - 0。另外, 虽然NaN不严格相等于自身, 但 Map 将其视为同一个键。

let map = new Map(); 
map.set(NaN, 123); 
map.get(NaN) // 123 
map.set(-0, 123); 
map.get(+0) // 123

10. 模块化

ES6首次引入模块化开发规范ES Module,让Javascript首次支持原生模块化开发。ES Module把一个文件当作一个模块,每个模块有自己的独立作用域,那如何把每个模块联系起来呢?核心点就是模块的导入与导出。

(1)export 导出模块
  • 正常导出:
// 方式一
export var first = 'test';
export function func() {
    return true;
}

// 方式二
var first = 'test';
var second = 'test';
function func() {
    return true;
}
export {first, second, func};
  • as关键字:
var first = 'test';
export {first as second};

as关键字可以重命名暴露出的变量或方法,经过重命名后同一变量可以多次暴露出去。

  • export default

export default会导出默认输出,即用户不需要知道模块中输出的名字,在导入的时候为其指定任意名字。

// 导出
export default function () {
  console.log('foo');
}
// 导入
import customName from './export-default';

注意: 导入默认模块时不需要大括号,导出默认的变量或方法可以有名字,但是对外无效。export default只能使用一次。

(2)import 导入模块
  • 正常导入:
import {firstName, lastName, year} from './profile';

导入模块位置可以是相对路径也可以是绝对路径,.js可以省略,如果不带路径只是模块名,则需要通过配置文件告诉引擎查找的位置。

  • as关键字:
import { lastName as surname } from './profile';

import 命令会被提升到模块头部,所以写的位置不是那么重要,但是不能使用表达式和变量来进行导入。

  • 加载整个模块(无输出)
import 'lodash'; //仅仅是加载而已,无法使用
  • 加载整个模块(有输出)
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

注意: import * 会忽略default输出

(3)导入导出复合用法
  • 先导入后导出
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
  • 整体先导入再输出以及default
// 整体输出
export * from 'my_module';
// 导出default,正如前面所说,export default 其实导出的是default变量
export { default } from 'foo';
// 具名接口改default
export { es6 as default } from './someModule';
(4)模块的继承
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

注意: export * 会忽略default。

11. 字符串方法

(1)includes()

includes():返回boolean值,字符串是否包含子串:

string.includes(searchvalue, start)

该方法有两个参数:

  • searchvalue:必需,要查找的字符串;
  • start:可选,设置从哪个索引位置开始查找,默认为 0。
let str = 'Hello world!';
 
str.includes('o')  // 输出结果:true
str.includes('z')  // 输出结果:false
str.includes('e', 2)  // 输出结果:false
(2)startsWith()

startsWith():返回boolean值,是否以指定子串开头。语法同includes()方法一样。

let str = 'Hello world!';
 
str.startsWith('Hello') // 输出结果:true
str.startsWith('Helle') // 输出结果:false
str.startsWith('wo', 6) // 输出结果:true
(3)endsWith()

endsWith():返回boolean值,是否以指定子串结尾:

string.endsWith(searchvalue, length)

该方法有两个参数:

  • searchvalue:必需,要搜索的子字符串;
  • length:设置字符串的长度,默认值为原始字符串长度 string.length。
let str = 'Hello world!';
 
str.endsWith('!')       // 输出结果:true
str.endsWith('llo')     // 输出结果:false
str.endsWith('llo', 5)  // 输出结果:true

可以看到,当第二个参数设置为5时,就会从字符串的前5个字符中进行检索,所以会返回true。

(4)repeat()

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

'x'.repeat(3)     // 输出结果:"xxx"
'hello'.repeat(2) // 输出结果:"hellohello"
'na'.repeat(0)    // 输出结果:""

如果参数是小数,会向下取整:

'na'.repeat(2.9) // 输出结果:"nana"

如果参数是负数或者Infinity,会报错:

'na'.repeat(Infinity)   // RangeError
'na'.repeat(-1)         // RangeError

如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。

'na'.repeat(-0.9)   // 输出结果:""

如果参数是NaN,就等同于 0:

'na'.repeat(NaN)    // 输出结果:""

如果repeat的参数是字符串,则会先转换成数字。

'na'.repeat('na')   // 输出结果:""
'na'.repeat('3')    // 输出结果:"nanana"

12. 数组方法

(1)reduce()

reduce() 方法对数组中的每个元素执行一个reducer函数(升序执行),将其结果汇总为单个返回值。其使用语法如下:

arr.reduce(callback,[initialValue])

reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

(1) callback (执行数组中每个值的函数,包含四个参数)

  • previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
  • currentValue (数组中当前被处理的元素)
  • index (当前元素在数组中的索引)
  • array (调用 reduce 的数组)

(2) initialValue (作为第一次调用 callback 的第一个参数。)

let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
})
console.log(arr, sum);

输出结果如下:

1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

再来加一个初始值看看:

let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
}, 5)
console.log(arr, sum);

输出结果如下:

5 1 0
6 2 1
8 3 2
11 4 3
[1, 2, 3, 4] 15

结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引,第一个作为初始值。如果提供initialValue,从索引0开始。

(2)filter()

filter()方法返回回调函数为true的元素组成的新数组。

let arr = [1, 2, 3, 4, 5]
arr.filter(item => item > 2) 
// 结果:[3, 4, 5]

可以使用filter()方法来移除数组中的undefined、null、NAN等值

let arr = [1, undefined, 2, null, 3, false, '', 4, 0]
arr.filter(Boolean)
// 结果:[1, 2, 3, 4]
(3)Array.from

Array.from 将类似数组(有迭代器)的可迭代对象创建为一个新的数组,不会改变原对象。

语法上有 3 个参数:

  • 必选,类似数组的对象;
  • 可选,加工函数,新生成的数组会经过该函数的加工再返回;
  • 可选,this 作用域,表示加工函数执行时 this 的值。
var obj = {0: 'a', 1: 'b', 2:'c', length: 3};
 
Array.from(obj, function(value, index){
  console.log(value, index, this, arguments.length);
  return value.repeat(3);   //必须指定返回值,否则返回 undefined
}, obj);

结果如图:

9d3741e58b34c4973bdf1fcebc83c67b.png

如果这里不指定 this,加工函数就可以是一个箭头函数。简写为:

Array.from(obj, (value) => value.repeat(3));
//  控制台打印 (3) ["aaa", "bbb", "ccc"]

除了上述 obj 对象以外,拥有迭代器的对象还包括 String、Set、Map 等,Array.from 都可以进行处理:

// String
Array.from('abc');                             // ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def']));           // ["abc", "def"]
// Map
Array.from(new Map([[1, 'ab'], [2, 'de']]));   // [[1, 'ab'], [2, 'de']]
(1)fill()

array.fill(value, start, end)方法:向已有数组中插入全部或部分相同的值,start或end,如果是负值,则将从负值加上数组的长度而得到的值开始,其参数如下:

  • value:必需。填充的值;
  • start:可选。开始填充位置;
  • end:可选。停止填充位置 (默认为 array.length)即数组末尾。
const arr = [0, 0, 0, 0, 0];
 
// 用5填充整个数组
arr.fill(5);
console.log(arr); // [5, 5, 5, 5, 5]
arr.fill(0);      // 重置
 
// 用5填充索引大于等于3的元素
arr.fill(5, 3);
console.log(arr); // [0, 0, 0, 5, 5]
arr.fill(0);      // 重置
 
// 用5填充索引大于等于1且小于等于3的元素
arr.fill(5, 3);
console.log(arr); // [0, 5, 5, 0, 0]
arr.fill(0);      // 重置
 
// 用5填充索引大于等于-1的元素
arr.fill(5, -1);
console.log(arr); // [0, 0, 0, 0, 5]
arr.fill(0);      // 重置

二、ES7 新特性(2016)

1. Array.includes()

includes() :返回boolean值,数组是否包含指定元素:

arr.includes(searchElement, fromIndex)

参数:

  • searchElement:必须,需要查找的元素值。
  • fromIndex:可选,开始索引查找目标值。若为负值,则从 array.length + fromIndex 的索引开始搜 (倒数 fromIndex 个索引,然后往后搜寻)。默认为 0。
[1, 2, 3].includes(2);  //  true
[1, 2, 3].includes(4);  //  false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

在 ES7 之前,使用 indexOf 来判断数组中是否包含某个指定值。但 indexOf 在语义上不够明确直观,同时 indexOf 内部使用 === 来判等,所以存在对 NaN 的误判,includes 则修复了这个问题:

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

2. 指数操作符

ES7 引入指数操作符 ,更方便进行指数计算,与 Math.pow() 等效:

Math.pow(2, 10));  // 1024
2**10;           // 1024

三、ES8 新特性(2017)

1. padStart()和padEnd()

padStart()和padEnd()方法用于补齐字符串的长度。如果某个字符串不够指定长度,会在头部或尾部补全。

(1)padStart()

padStart(length,addStr):用于头部补全。

  • length:补全生效的最小长度,原字符串长度 >= 指定最小长度,则返回原字符串
  • addStr?:用来补全的字符串,达不到长度会重复,默认使用空格补全长度
'x'.padStart(1, 'ab') // 'x'
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padStart(4) // 'a   '
补齐指定三位数 页码问题
"1".padStart(3, '0')   // 输出结果: '001'
"15".padStart(3, '0')  // 输出结果: '015'
(2)padEnd()

padEnd(length,addStr):用于尾部补全。

  • length:补全生效的最小长度,原字符串长度 >= 指定最小长度,则返回原字符串
  • addStr:用来补全的字符串,达不到长度会重复,默认使用空格补全长度
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

2. Object.values()和Object.entries()

对ES5中Object.keys方法遍历补充手段,供for…of循环使用。返回由给定对象的自身可枚举的属性(不含继承的和Symbol属性)组成的数组,数组元素顺序和正常循环遍历对象时一致,值如下:

  • Object.keys():返回包含对象键名的数组;
  • Object.values():返回包含对象键值的数组;
  • Object.entries():返回包含对象键名和键值的数组。
let obj = { 
  id: 1, 
  name: 'hello', 
  age: 18 
};
console.log(Object.keys(obj));   // 输出结果: ['id', 'name', 'age']
console.log(Object.values(obj)); // 输出结果: [1, 'hello', 18]
console.log(Object.entries(obj));   // 输出结果: [['id', 1], ['name', 'hello'], ['age', 18]

注意:Object.keys()返回数组中的值都是字符串,不是字符串的key值会转化为字符串。

3. 函数扩展

ES2017 规定函数参数列表结尾可以为逗号:

function person( name, age, sex, ) {}

主要作用:方便使用git进行多人协作开发时修改同一个函数减少不必要的行变更。

四、ES9 新特性(2018)

1. for await…of

for await...of方法被称为异步迭代器,主要用来遍历异步对象。

for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,类数组,Map, Set和自定义的异步或者同步可迭代对象。这个语句只能在 async function内使用:

function Gen (time) {
  return new Promise((resolve,reject) => {
    setTimeout(function () {
       resolve(time)
    },time)
  })
}
 
async function test () {
   let arr = [Gen(2000),Gen(100),Gen(3000)]
   for await (let item of arr) {
      console.log(Date.now(),item)
   }
}
test()

输出结果:

9720196e61fe4af302c6786199443885.png

2. Promise.finally

Promise.finally() :无论 Promise 最终成功或失败都执行:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const one = '1';
    reject(one);
  }, 1000);
});
 
promise
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

finally() 函数不接受参数,finally() 内部通常不知道 promise 实例的执行结果,所以通常在 finally() 方法内执行的是与 promise 状态无关的操作。

3. 对象的扩展运算符

ES6中只能作用于数组,ES2018中扩展运算符可以作用于对象:

(1)将元素组织成对象

const obj = {a: 1, b: 2, c: 3};
const {a, ...rest} = obj;
console.log(rest);    // 输出 {b: 2, c: 3}
 
(function({a, ...obj}) {
  console.log(obj);    // 输出 {b: 2, c: 3}
}({a: 1, b: 2, c: 3}));

(2)将对象扩展为元素

const obj = {a: 1, b: 2, c: 3};
const newObj ={...obj, d: 4};
console.log(newObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

(3)可以用来合并对象

const obj1 = {a: 1, b:2};
const obj2 = {c: 3, d:4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

4. 对象的 Rest

在对象的解构中,除了已经指定的属性之外,rest将会拷贝对象其他的所有可枚举属性:

const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;
 
console.log(rest); // {bar: 2, baz: 3}

如果用在函数参数中,rest 表示所有剩下的参数:

function func({param1, ...rest}) {
    return rest;
}
 
console.log(func({param1:1, b:2, c:3, d:4}))  // {b: 2, c: 3, d: 4}

注意,在对象字面量中,rest运算符只能放在对象的最顶层,并且只能使用一次,要放在最后:

const {...rest, foo} = obj; // Uncaught SyntaxError: Rest element must be last element
const {foo, ...rest1, ...rest2} = obj; // Uncaught SyntaxError: Rest element must be last element

五、ES10 新特性(2019)

1. trimStart() 和 trimEnd()

ES10之前trim()用于移除字符串首尾空白符。ES10中trimStart()和trimEnd() 方法用于移除字符串首、尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。

trimStart() :返回删除头部空白的新字符串,不会修改原始字符串:
trimEnd() :返回删除尾部空白的新字符串,不会修改原始字符串:

const s = '  abc  ';
 
s.trimStart()   // "abc  "
s.trimEnd()   // "  abc"

注意,这两个方法都不适用于null、undefined、Number类型。

2. flat()和flatMap()

(1)flat()

在ES2019中,flat()创建并返回一个新数组,元素本身也是数组会被打平填充到返回的数组中:

[1, [2, 3]].flat()        // [1, 2, 3]
[1, [2, [3, 4]]].flat()   // [1, 2, [3, 4]]
  • flat(n):n为 number 类型,表示要打平的层级数,默认只会打平一级嵌套:
  • 如果数组中存在空项,会直接跳过:
  • 如果传入的参数小于等于0,就会返回原数组:
[1, [2, [3, 4]]].flat(2)  // [1, 2, 3, 4]
[1, [2, , 3]].flat());    //  [1, 2, 3]
[1, [2, [3, [4, 5]]]].flat(0);    //  [1, [2, [3, [4, 5]]]]
[1, [2, [3, [4, 5]]]].flat(-10);  //  [1, [2, [3, [4, 5]]]]
(2)flatMap()

flatMap()方法使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 几乎相同,但 效率稍微高一些。

[1, 2, 3, 4].flatMap(x => x * 2);      //  [2, 4, 6, 8]
[1, 2, 3, 4].flatMap(x => [x * 2]);    //  [2, 4, 6, 8]
 
[1, 2, 3, 4].flatMap(x => [[x * 2]]);  //  [[2], [4], [6], [8]]
[1, 2, 3, 4].map(x => [x * 2]);        //  [[2], [4], [6], [8]]

3. Object.fromEntries()

Object.fromEntries():可以把键值对列表转换为一个对象。该方法相当于 Object.entries() 方法的逆过程(对象转为二维数组)。

const object = { key1: 'value1', key2: 'value2' }
 
const array = Object.entries(object)  // [ ["key1", "value1"], ["key2", "value2"] ]
Object.fromEntries(array)             // { key1: 'value1', key2: 'value2' }

使用该方法主要有以下两个用途:

(1)将数组转成对象
const entries = [
  ['foo', 'bar'],
  ['baz', 42]
]
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }
(2)将 Map 转成对象
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
])
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }

4. Symbol描述

通过 Symbol() 创建符号时,可以通过参数提供字符串作为描述:

let dog = Symbol("dog");  // dog 为描述

在 ES2019 之前,获取一个 Symbol 值的描述需要通过 String 方法 或 toString 方法:

String(dog);              // "Symbol(dog)" 
dog.toString();           // "Symbol(dog)"

ES2019 补充了属性 description,用来直接访问描述

dog.description;  // dog

5. toString()

ES2019 对函数的 toString() 方法进行了扩展,以前这个方法只会输出函数代码,但会省略注释和空格。ES2019 的 toString()则会保留注释、空格等,即输出的是原始代码:

function sayHi() {
  /* dog */
  console.log('wangwang');
}
 
sayHi.toString();  // 将输出和上面一样的原始代码

6. catch

在 ES2019 以前,catch 会带有参数,但是很多时候 catch 块是多余的。而现在可以不带参数:

// ES2019 之前
try {
   ...
} catch(error) {
   ...
}
 
// ES2019 之后
try {
   ...
} catch {
   ...
}

六、ES11 新特性(2020)

1. BigInt

在 JavaScript 中,数值类型 Number 是 64 位浮点数,所以计算精度和表示范围都有一定限制。ES2020 新增了 BigInt 数据类型,这也是 JavaScript 引入的第八种基本类型。BigInt 可以表示任意大的整数。其语法如下:

BigInt(value);

其中 value 是创建对象的数值。可以是字符串或者整数。

在 JavaScript 中,Number 基本类型可以精确表示的最大整数是253。因此早期会有这样的问题:

let max = Number.MAX_SAFE_INTEGER;    // 最大安全整数
 
let max1 = max + 1
let max2 = max + 2
 
max1 === max2   // true

有了BigInt之后,这个问题就不复存在了:

let max = BigInt(Number.MAX_SAFE_INTEGER);
 
let max1 = max + 1n
let max2 = max + 2n
 
max1 === max2   // false

可以通过typeof操作符来判断变量是否为BigInt类型(返回字符串"bigint"):

typeof 1n === 'bigint'; // true 
typeof BigInt('1') === 'bigint'; // true

还可以通过Object.prototype.toString方法来判断变量是否为BigInt类型(返回字符串"[object BigInt]"):

Object.prototype.toString.call(10n) === '[object BigInt]';    // true

注意,BigInt 和 Number 不是严格相等的,但是宽松相等:

10n === 10 // false 
10n == 10  // true

Number 和 BigInt 可以进行比较:

1n < 2;    // true 
2n > 1;    // true 
2 > 2;     // false 
2n > 2;    // false 
2n >= 2;   // true

2. 空值合并运算符(??)

在编写代码时,如果某个属性不为 null 和 undefined,那么就获取该属性,如果该属性为 null 或 undefined,则取一个默认值:

const name = dogName ? dogName : 'default';

可以通过 || 来简化:

const name =  dogName || 'default';

但是 || 的写法存在一定的缺陷,当 dogName 为 0 或 false 的时候也会走到 default 的逻辑。所以 ES2020 引入了 ?? 运算符。只有 ?? 左边为 null 或 undefined时才返回右边的值:

const dogName = false; 
const name =  dogName ?? 'default';  // name = false;

3. 可选链操作符(?.)

又称 链判断运算符 ,开发中经常需要获取深层次属性,例如 system.user.addr.province.name。在获取 name 属性前需要一步步的判断前面的属性是否存在,否则并会报错:

const name = (system && system.user && system.user.addr && system.user.addr.province && system.user.addr.province.name) || 'default';

简化为:
const name = system?.user?.addr?.province?.name || 'default';

为了简化上述过程,使用?.,在引用为null 或 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。

可选链有以下三种形式:

a?.[x]
// 等同于
a == null ? undefined : a[x]
 
a?.b()
// 等同于
a == null ? undefined : a.b()
 
a?.()
// 等同于
a == null ? undefined : a()

在使用TypeScript开发时,这个操作符可以解决很多问题。

4. Promise.allSettled

Promise.allSettled:参数接受一个 Promise 的数组,返回一个新的 Promise。不管是否处理成功。

下面使用 allSettled 实现的一段代码:

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// 返回结果:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

5. String.matchAll()

matchAll() 是新增的字符串方法,它返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。因为返回的是遍历器,所以通常使用for…of循环取出。

for (const match of 'abcabc'.matchAll(/a/g)) { // 为每次匹配调用回调
    console.log(match)
}
//["a", index: 0, input: "abcabc", groups: undefined]
//["a", index: 3, input: "abcabc", groups: undefined]

需要注意,该方法的第一个参数是一个正则表达式对象,如果传的参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp 。另外,RegExp必须是设置了全局模式g的形式,否则会抛出异常 TypeError。

七、ES12 新特性(2021)

1. String.replaceAll()

replaceAll()方法会返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。

let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi')    // hi world, hi ES12
string.replaceAll('hello','hi')  // hi world, hi ES12

注意的是,replaceAll 在使用正则表达式的时候,如果非全局匹配(/g),会抛出异常:

let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi') 
// Uncaught TypeError: String.prototype.replaceAll called with a non-global

2. 数字分隔符

数字分隔符可以在数字之间创建可视化分隔符,通过 _下划线来分割数字,使数字更具可读性,可以放在数字内的任何地方:

const money = 1_000_000_000
//等价于
const money = 1000000000

该新特性同样支持在八进制数中使用:

const number = 0o123_456
//等价于
const number = 0o123456

3. Promise.any

Promise.any是 ES2021 新增的特性,它接收一个 Promise 可迭代对象(例如数组),只要其中的一个 promise 成功,就返回那个已经成功的 promise 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.resolve('result'),
]
 
Promise.any(promises).then((value) => {
  console.log('value: ', value)
}).catch((err) => {
  console.log('err: ', err)
})
 
// 输出结果:value:  result

如果所有传入的 promises 都失败:

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.reject('ERROR C'),
]
 
Promise.any(promises).then((value) => {
  console.log('value:', value)
}).catch((err) => {
  console.log('err:', err)
  console.log(err.message)
  console.log(err.name)
  console.log(err.errors)
})

输出结果:

err:AggregateError: All promises were rejected
All promises were rejected
AggregateError
["ERROR A", "ERROR B", "ERROR C"]

4. 逻辑赋值操作符

ES12中新增了几个逻辑赋值操作符,可以用来简化一些表达式:

// 等同于 a = a || b
a ||= b;

// 等同于 c = c && d
c &&= d;

// 等同于 e = e ?? f
e ??= f;

八、ES13 新特性(2022)

1. Object.hasOwn()

在ES2022之前,可以使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于对象。

Object.hasOwn 特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法:

const example = {
  property: '123'
};
 
console.log(Object.prototype.hasOwnProperty.call(example, 'property'));
console.log(Object.hasOwn(example, 'property'));

2. Array.at()

at() 是一个数组方法,用于通过给定索引来获取数组元素。当给定索引为正时,这种新方法与使用括号表示法访问具有相同的行为。当给出负整数索引时,就会从数组的最后一项开始检索:

const array = [0,1,2,3,4,5];
 
console.log(array[array.length-1]);  // 5
console.log(array.at(-1));  // 5
 
console.log(array[array.lenght-2]);  // 4
console.log(array.at(-2));  // 4

除了数组,字符串也可以使用at()方法进行索引:

const str = "hello world";
 
console.log(str[str.length - 1]);  // d
console.log(str.at(-1));  // d

3. error.cause

在 ECMAScript 2022 规范中,new Error() 中可以指定导致它的原因:

function readFiles(filePaths) {
  return filePaths.map(
    (filePath) => {
      try {
        // ···
      } catch (error) {
        throw new Error(
          `While processing ${filePath}`,
          {cause: error}
        );
      }
    });
}

4. Top-level Await

在ES2017中,引入了 async 函数和 await 关键字,以简化 Promise 的使用, await 关键字只能在 async 函数内部使用。在异步函数之外使用 await 就会报错:SyntaxError - SyntaxError: await is only valid in async function

顶层 await 允许我们在 async 函数外面使用 await 关键字。它允许模块充当大型异步函数,通过顶层 await,这些 ECMAScript 模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行。

由于 await 仅在 async 函数中可用,因此模块可以通过将代码包装在 async 函数中来在代码中包含 await

// a.js
  import fetch  from "node-fetch";
  let users;
 
  export const fetchUsers = async () => {
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users =  resp.json();
  }
  fetchUsers();
 
  export { users };
 
  // usingAwait.js
  import {users} from './a.js';
  console.log('users: ', users);
  console.log('usingAwait module');

我们还可以立即调用顶层async函数(IIAFE):

import fetch  from "node-fetch";
  (async () => {
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users = resp.json();
  })();
  export { users };

这样会有一个缺点,直接导入的 usersundefined,需要在异步执行完成之后才能访问它:

// usingAwait.js
import {users} from './a.js';
 
console.log('users:', users); // undefined
 
setTimeout(() => {
  console.log('users:', users);
}, 100);
 
console.log('usingAwait module');

当然,这种方法并不安全,因为如果异步函数执行花费的时间超过100毫秒, 它就不会起作用了,users 仍然是 undefined

另一个方法是导出一个 promise,让导入模块知道数据已经准备好了:

//a.js
import fetch  from "node-fetch";
export default (async () => {
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  users = resp.json();
})();
export { users };
 
//usingAwait.js
import promise, {users} from './a.js';
promise.then(() => { 
  console.log('usingAwait module');
  setTimeout(() => console.log('users:', users), 100); 
});

虽然这种方法似乎是给出了预期的结果,但是有一定的局限性:导入模块必须了解这种模式才能正确使用它

而顶层await就可以解决这些问题:

// a.js
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = resp.json();
  export { users};
 
  // usingAwait.js
  import {users} from './a.mjs';
 
  console.log(users);
  console.log('usingAwait module');

顶级 await 在以下场景中将非常有用:

  • 动态加载模块:
const strings = await import(`/i18n/${navigator.language}`);
  • 资源初始化:
const connection = await dbConnector();
  • 依赖回退:
let translations;
try {
  translations = await import('https://app.fr.json');
} catch {
  translations = await import('https://fallback.en.json');
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值