springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j)
巧用esnext可以在开发过程中提升效率和优化性能,简单分享下esnext部分知识点(根据自己理解分享,不完全按es版本顺序)
【ps:其实ESNext是⼀个泛指, 它永远指向下⼀个版本. ⽐如当前最新版本是ES2021, 那么ESNext指的就是2022年6⽉将要发布的标准。
但在这里因为想分享的“新”特性自es6到最近的都有涉及,所以姑且统称esnext(不是指下一个新标准的内容我没那么快...也不是指js的库)】
一、解构赋值
1、数组结构
在之前给数组赋值一般会这样写:
const arr = [1,2,3]
let a = arr[0]
let b = arr[1]
let c = arr[2]
console.log(a,b,c) // 1 2 3
现在用es6可以这样写:
let [a,b,c,d] = [1,2,3,4]
console.log(a,b,c,d) // 1 2 3 4
// 还可以这样写
let [a,[b,c],d] = [1,[2,3],4]
也就是说,只要等号两边的模式相同,左边的变量就可以被赋值与右边对应位置上的值,这本质上属于“模式匹配”。
如果等号左边的变量在等号右边的对应位置上没有找到有效值,就会解构不成功,比如像这样:
let [a,b] = [1] // 变量b没有在右边对应位置上找到有效的值,b会等于undefined
而如果反过来,等号左边只匹配等号右边数组的一部分,这种情况叫不完全结解构,并且可以解构成功:
let [a,b] = [1,2,3] // 匹配前两项
console.log(a,b) // 1 2
let [x,,y] = [4,5,6] // 匹配第0项和第2项
consoleg(x,y) // 4 6
2、对象解构
对象解构与数组解构不同的地方在于:数组是有次序的,所以数组解构是按位置取值;而对象的属性是没有次序的,所以对象解构是用变量名对应属性名的方式来取值,也就是先找到同名属性,再赋值给对应变量,像这样:
let {a,b} = {a: 'a1', b: 'b1'}
console.log(a,b) // a1 b1
// 次序不同,找到同名属性就可以赋值给对应变量
let {d,c} = {c: 'c1', d: 'd1'}
console.log(c,d) // c1 d1
// 错误示例:
let {f} = {a: 'a1', b: 'b1'}
console.log(f) // undefined
如果想要使用不同的变量名来获取等号右边的某个属性值,也可以这样写:
let {a:f} = {a: 'a1', b: 'b1'}
// 这里的a其实是匹配模式,f才是真正的变量,所以被赋值的是f。
console.log(f) // a1
3、默认值
数组解构和对象解构都是允许指定默认值,这在不确定等号右边的数据结构的时候很好用:
//数组解构的默认值
let [a = 1, b = 2] = [2]
console.log(a,b) // 2 2
let [a = 1, b = 2] = [null, 5]
// 因为null并不严格等于undefined,因此a的默认值不生效
console.log(a,b) // null 5
// 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
let [a = b, b = 3] = []
// b在作为a的默认值时还未被声明,所以会报错
console.log(a,b) // ReferenceError: b is not defined
let [a = 1, b = a] = []
let [a, b = a] = [1]
// b使用a作为默认值时,a已经被定义并且有值
console.log(a,b) // 1 1
// 对象结构的默认值
let {a = 1} = {}
console.log(a) // a
let {a = 2} = {a: null}
console.log(a) // null
let {a: f = 2} = {a: 100}
console.log(f) // 100
ps:除上述之外还有一些其他解构赋值如字符串解构、数值和布尔值解构、函数参数解构等
二、Spread / Rest 操作符
放在一起说是因为,这“两个”操作符实际上都是... ,有意思的地方在于在不同的场景中使用,...的作用是完全相反的。
Spread操作符其实就是我们常说的扩展运算符,作为扩展运算符的时候它的画风一般都是这样的:
let arr = [1,2,3]
console.log(...arr) // 1 2 3
let str = 'string'
console.log(...str) // s t r i n g
应用起来也非常顺手:
let arr = [1,9,5,8,2]
let max = Math.max(...arr)
console.log(max) // 9
let arr1 = [1,2]
let arr2 = [3,4,5]
let arr = [...arr1, ...arr2]
console.log(arr) // [1,2,3,4,5]
let obj = { a:1, b:2, c:3, d:4 }
obj = { ...obj, c:5, d:7, e:9 }
console.log(obj) // {a: 1, b: 2, c: 5, d: 7, e: 9}
let obj1 = {a:1, b:2}
let obj2 = {a:5, c:3, d:4}
let obj = {...obj1, ... obj2}
console.log(obj) // {a: 5, b: 2, c: 3, d: 4}
而在某些场景下,...会起到相反的作用,这时候它就是作为Rest 操作符(剩余运算符),比如说:
function add(...arr) {
console.log(arr);
}
add( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
// 在上述add函数的传参中,它起到的是“收集"的作用
// 与解构赋值结合使用:收集其余参数
let arr = [1,2,3,4,5,6];
let [a,b,...c] = arr;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]
// rest参数可理解为剩余的参数,所以必须在最后一位定义,如果定义在中间会报错。
let arr = [1,2,3,4,5,6];
let [a,b,...c,d,e] = arr; // Uncaught SyntaxError: Rest element must be last element
三、模板字符串
模板字符串非常好用也很基础,其实没什么特别需要强调的,只是我们日常多使用于拼接字符串和变量,其实模板字符串还可以插入表达式,也可以进行运算,以及引用对象的属性,甚至可以调用函数。
需要注意的是,如果在多行字符串中有空格和缩进,那么它们都会被保留在输出中。
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// 字符串中调用函数
`${fn()}`
// 字符串中使用表达式
const name = '小明';
const score = 59;
const result = `${name}的考试成绩${score > 60?'不':''}及格`;
四、set
set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
set的属性和方法:
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1,2,3,1,2,3]);
console.log(s1) // {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
由于集合中元素的唯一性,所以在实际应用中,可以使用set来实现数组去重:
let arr = [1,2,3,2,1]
arr = Array.from(new Set(arr)) // {1, 2, 3} 使用Array.form()方法来将数组集合转化为数组
console.log(arr) // [1, 2, 3]
// (和解构赋值结合使用)合并数组时去重:
let a = [1,2,3];
let b = [1,5,6];
let c = [...new Set([...a,...b])]; // new Set()去重
console.log(c) // [1,2,3,5,6]
五、Array.prototype.includes
includes()是es7引入的新特性,用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false。
let arr = [1,2,3]
let a = arr.includes(2)
let b = arr.includes(6)
console.log(a,b) // true false
需要注意的是:
①使用
includes()
比较字符串和字符时是区分大小写的②0 的值将全部视为相等,与符号无关(即 -0 与 0 和 +0 相等),但
false
不被认为与 0 相等。
includes可以查找指定位置的元素值
let arr = [1,2,3,4]
let a = arr.includes(2, 1) // true
let b = arr.includes(4, 4) // false
let c = arr.includes(4, -1) // true
console.log(c)
从fromIndex
索引处开始查找valueToFind
。如果为负值,则按升序从array.length + fromIndex
的索引开始搜 (即使从末尾开始往前跳fromIndex
的绝对值个索引,然后往后搜寻)。默认为 0。
说到includes()就想起另外一个同样非常便捷好用的对数组的查询方法—find()。
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回undefined。简单举个例子:
let list = [
{id: 100, name: '小明', age: 10},
{id: 101, name: '小红', age: 12},
{id: 102, name: '小蓝', age: 14}
]xiao
let a = arr.find(el => el.id === 102)
console.log(a) // {id: 102, name: '小蓝'}
let b = arr.find(el => el.age > 14)
console.log(b) // undefined (未找到age大于14的值,返回undefined)
let c = arr.find(el => el.age > 11)
// 符合age>11的条件的值有两个,只返回第一个,并且返回第一个后就会停止遍历
console.log(c) // {id: 101, name: '小红', age: 12}
六、padStart()和padEnd()
简单点说其实padStart()和padEnd()方法就是用于补齐字符串的长度。
假设某个字符串未达到指定长度,使用padStart()会在字符串头部(左边)开始补全,padEnd()则从字符串尾部(右边)开始补全。
举个例子:
let str = '1'
// 从左填充
let str2 = str.padStart(3, '00')
console.log(str2) // 001
// 从右填充 (需要达到指定长度3,会重复填充'0')
let str3 = str.padEnd(4, '0')
console.log(str3) // 1000
// 字符串长度大于指定数值长度
let str4 = 'abcdefg'
let str5 = str4.padStart(4, 'h')
// 返回字符串本身
console.log(str5) // abcdefg
// 如果填充字符串太长,使填充后的字符串长度超出了目标长度
let str6 = str4.padStart(10, 'hijklmn')
// 保留填充字符串最左侧部分,其余部分被截断,这里原字符串长度是7,填充字符串长度是7,目标长度是10,将会从填充字符串最左边开始保留3个字符填充到原字符串中,也就是'hij'
console.log(str6) // hijabcdefg
// padEnd同理
let str7 = str4.padEnd(10, 'hijklmn')
console.log(str7) // abcdefghij
// 不指定填充字符, 默认""
let str8 = str4.padStart(10)
console.log(str8) // ' abcdefg'
console.log(str8.length) // 10
let str9 = str4.padEnd(10)
console.log(str9) // 'abcdefg '
console.log(str9.length) // 10
七、Object.keys()、Object.values()、Object.entries()、Object.fromEntries()
在ES5中就引入了Object.keys方法,在ES8中引入了跟Object.keys配套的Object.values和Object.entries。
- Object.keys():返回包含对象键名的数组;
- Object.values():返回包含对象键值的数组;
- Object.entries():返回包含对象键名和键值的数组。
let obj = { id: 101, name: '小明', age: 11, grade: '二年级' }
console.log(Object.keys(obj)); // ['id', 'name', 'age', 'grade']
console.log(Object.values(obj)); // [101, '小明', 11, '二年级']
console.log(Object.entries(obj)); // [['id', 101],['name', '小明'],['age', 10],['grade', '二年级']]
// 当我们使用数字键时,按照键的数字顺序返回值。
var obj2 = { 8: 'a', 2: 'b', 5: 'c' };
console.log(Object.values(obj2)); // ['b', 'c', 'a']
// 非对象参数将被强制转化为一个对象
console.log(Object.values('foo')); // ['f', 'o', 'o']
Object.fromEntries()是ES10中更新的新特性,Object.fromEntries 方法把键值对列表转换为一个对象。它其实相当于 Object.entries() 方法的逆过程:
let obj = { a: 1, b: 2 }
let arr = Object.entries(obj)
console.log(arr) // [['a', 1], ['b', 2]]
let obj1 = Object.fromEntries(arr)
console.log(obj1) // {a: 1, b: 2}
Object.fromEntries()方法可以用于将数组转换成对象:
let arr = [[11, '小明'], [13, '小蓝']]
console.log(Object.fromEntries(arr)) // {11: '小明', 13: '小蓝'}
八、trim()、trimStart() 和 trimEnd()
trim方法是指删除字符串中的头尾空格,ES10新出了trimStart和trimEnd方法,跟trim一样也是删除空格,区别在于:
trimStart方法删除字符串的开头的连续空白符,别名trimLeft;
trimEnd方法删除字符串结尾的连续空白符,别名trimRight。
let str = ' 小明 '
console.log(str.trim()) // '小明'
console.log(str.trimStart()) // '小明 '
console.log(str.trimEnd()) // ' 小明'
九、flat()和flatMap()
在ECMA文档中是这样介绍的:flat
and flatMap
on Array.prototype
for flattening arrays(Array.prototype上的flat和flatMap用于扁平化数组),就像这样:
let arr = [1,[2,[3,4]]]
console.log(arr.flat()) // [1, 2, [3, 4]]
// 在不传参时,flat()默认只会打平一级嵌套,如果想要处理更多层级,就得给flat()传个数值参数:
let arr1 = arr.flat(2)
console.log(arr1) // [1, 2, 3, 4]
console.log(arr1.map(x => [x * 2]);) // [[2], [4], [6], [8]]
// 区别于map(),flatMap得到的是一个扁平化的数组
console.log(arr1.flatMap(x => [x * 2]);) // [2, 4, 6, 8]
十、可选链操作符
可选链操作符( ?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
操作符的功能类似于 .
链式操作符,不同之处在于,在引用为空null或者undefined的情况下不会引起错误,该表达式短路返回值是 undefined。
let obj = {
a: {
b: {
c: 100,
d: 200
}
}
}
// 这种结构下如果我们想取属性d的值,一般会写
let value = obj.a.b.d
console.log(value) // 200
// 这时候如果前面某一层缺失,就会报错:
let obj1 = {
a: {}
}
let value = obj.a.b.d
console.log(value) // Uncaught TypeError: Cannot read properties of undefined (reading 'd')
// 如果要不报错,就要一层一层地判断
let value = obj && obj.a && obj.a.b && obj.a.b.d || 0
let value1 = obj1 && obj1.a && obj1.a.b && obj1.a.b.d || 0
console.log(value) // 200
console.log(value1) // 0
为了简化这个写法,ES11引入了引入了可选链操作符,化繁为简可以这样写:
let obj = {
a: {
b: {
c: 100,
d: 200
}
}
}
let obj1 = {
a: {}
}
let value = obj?.a?.b?.d
console.log(value) // 200
let value1 = obj1?.a?.b?.d
console.log(value1) // undefined
a?.b
// 等同于
a ? a.b : undefined
a?.[x]
// 等同于
a ? a[x] : undefined
十一、空值合并操作符
空值合并操作符(??
)是一个逻辑操作符,当左侧的操作数为null或者undefined时,返回其右侧操作数,否则返回左侧操作数。
let value = 0
let val = value ? value || 0
// 也可以写成
let val = value || 0
// 但是直接用||会有一定的缺陷,因为||是一个布尔逻辑运算符,左侧会被强制转换成布尔值,任何假值(0, '', NaN, null, undefined)都会被认为是false,从而不被返回。如果0或''甚至NaN需要作为有效值,就无法使用||
// 其实也就是
let val = value !== null && value !== undefined ? value : 1
// 等同于
let val = value ?? 1
console.log(val) // 0
十二、String.prototype.replaceAll()
replaceAll方法返回一个新字符串,新字符串所有满足pattern的部分都已被replacement替换。pattern可以是一个字符串或一个RegExp,replacement可以是一个字符串或一个在每次匹配被调用的函数。
简单点说replaceAll()方法就是可以用来全局替换指定的字符串,这弥补了replace()方法pattern是字符串时,仅替换第一个匹配项的不足。
let str = "abcabc"
console.log(str.replaceAll('a', '')) // bcbc
// 如果使用正则表达式,则必须使用全局匹配(/g),否则会报错
console.log(str.replaceAll(/a/g, '')) // bcbc
console.log(str.replaceAll(/a/, '')) // Uncaught TypeError: String.prototype.replaceAll called with a non-global RegExp argument(在调用String.prototype.replaceAll时使用了一个非全局性的正则参数)
十三、函数参数默认值
在ES6之前函数是不支持默认参数的,ES6的时候就实现了这个支持,当不传入参数或参数为undefined时,默认值就会生效
function add(x = 0, y = 0) {
console.log(x, y);
}
add(1, 2); // 1 2
add(1) // 1 0
add() // 0 0
add(null, undefined) // null 0
当给函数的参数设置了默认值之后,参数在被初始化时将形成一个独立作用域,初始化完成后作用域消解
let x = 1;
function add(x, y = x) {
console.log(y); // 2
}
// 函数内打印出来的y会等于2,因为函数在被调用时参数x,y形成了一个暂时的独立的作用域,x被作为默认值赋值给y,这时的x是函数参数独立作用域中的x而不是函数外定义的x
add(2);
函数length属性通常时用来表示函数参数的个数,当使用函数参数默认值时,函数length表示的就是第一个有默认值参数之前的普通参数个数:
const add1 = function(x, y) {};
console.log(add1.length); // 2
const add2 = function(x, y = 1) {};
console.log(add2.length); // 1
const add3 = function(x = 1, y) {};
console.log(add3.length); // 0
参考来源:MDN、文档、《es6标准入门》和掘金、知乎等社区