目录
- 历史包袱
- 向前看
- Array.prototype.includes()
- 指数运算符
- async/await
- Object.values()
- Object.entrys()
- String.prototype.padStart() & String.prototype.padEnd()
- Object.getOwnPropertyDescriptors()
- SharedArrayBuffer & Atomics
- for await
- Promise.prototype.finally()
- 正则表达式之“对捕获组命名”
- Array.prototype.flat() & Array.prototype.flatMap()
- String.prototype.trimStart() 等
- Object.fromEntries()
- Symbol.prototype.description
- String.prototype.matchAll()
- try-catch 写法调整
- 新的基本数据类型 BigInt
- globalThis
- 私有成员
- (可选链操作符)?.
- (空值合并运算符)??
- 静态方法
- 逻辑或赋值、逻辑且赋值
历史包袱
js 是一个独特的语言,因为他大部分情况下运行在浏览器里,你不能确定客户的浏览器是什么版本
而且有些时候,可以确定客户用的就是非常非常老的版本
他的历史包袱太重了:
- “不好的特性”不能被删掉。删掉之后,老项目就不能在新浏览器里正常运行
- 新特性难推广。既然客户端不支持,我干嘛要学习呢(“什么?Babel?我不用新特性不照样写项目?)
但新特性还是在不断地推出
es6 是非常重要且具有标志意义的一个“新特性集”
主要是因为它的内容多,真的使 js 改头换面
关于 es6,阮一峰老师写了一本《ES6 入门教程》
说是“入门”,其实非常详细、易懂,强烈推荐
但 es6 毕竟已经发布六年了,这六年里,js 还在不断成长
尽管速度慢了下来,但每一个新特性都令我激动,希望能尽快在项目中使用
尽管事实上,很难在实际项目中使用(客户端不支持)
但 es6 当年也是“广泛不支持”,现在也已经广泛使用
向前看
以下内容,你的客户的浏览器很可能不支持,慎用
最新的 node 和 最新的 chrome 支持程度也不一样
用之前一定要试一下你的环境
Array.prototype.includes()
用来判断一个数组是否包含某元素
以前:
[1,2,3].indexOf(3) > -1
现在(或以后):
[1,2,3].includes(3)
指数运算符
以前:
Math.pow(2, 5) // 2 * 2 * 2 * 2 * 2
现在(或以后):
2 ** 5
async/await
尽管 Promise 看起来解决了“回调地狱”,但写法依然不够好看
以前:
axios(url)
.then(res => {
doSth(res)
return axios.get(url2)
})
.then(res => {
doSth(res)
})
现在(或以后):
async function main(){
let res = await axios(url)
doSth(res)
res = await axios(url2)
doSth(res)
})
main()
注意!await 关键字只能在 async 函数内使用:
普通函数:
function f1() {}
let f2 = () => {}
let f3 = function() {}
let obj = {
f4: function(){},
f5(){}
}
async 函数:
async function f1() {}
let f2 = async () => {}
let f3 = async function() {}
let obj = {
f4: async function() {},
async f5() {}
}
了解更多 async/await 的用法,可以看阮一峰老师的这篇
Object.values()
Object.values({
a: 1,
b: 2,
c: {
name: 'c'
}
}) // [1, 2, { name: 'c' }]
Object.entrys()
String.prototype.padStart() & String.prototype.padEnd()
填充字符串到指定长度
let a = '1'
a.padStart(4) // ' 1'
a // '1'
a.padStart(4, '0') // '0001'
a.padEnd(4) // '1 '
a.padEnd(4, '0') // '1000'
Object.getOwnPropertyDescriptors()
如果你用过 Object.defineProperty()
,那对这个函数应该不陌生
如果你没用过,那就先用用Object.defineProperty()
.
用过你就知道 getOwnPropertyDescriptors
有什么用了
SharedArrayBuffer & Atomics
mdn 对此有详细的介绍:
for await
先说它不是什么
假如你有一个循环操作,每次循环都要删除、修改、更新(他们都是异步的)一些东西,你可以:
async function main() {
for(let item of list) {
await drop(item.id)
await update(xxx)
await insert(xxx)
}
}
这样的写法没问题,但跟 for await 没任何关系
for await 的作用在于:
let process = [drop(), update(), insert()] // drop, update, insert 都是异步操作,都返回 Promise 对象
for await (let result of process) {
// ...
}
当然,上面的操作要在一个 async 函数里进行
drop 结束了,会执行第一次循环
第一次循环结束了,且 update 也结束了,会执行第二次
……
Promise.prototype.finally()
以前,你可以对 Promise 对象进行 then(成功)和 catch(失败)操作
现在,可以使用 finally
,不论成功还是失败,都会执行
正则表达式之“对捕获组命名”
你可能会写一个这样的正则表达式来检验时间(时:分:秒)格式:
let re = /\d{2}:\d{2}:\d{2}/
re.test('11:27:04') // true
或者,你需要取出目标字符串的时、分、秒:
let re = /(\d{2}):(\d{2}):(\d{2})/ // 给每个“待取出”部分,加括号
let result = re.exec('11:27:04')
result[0] // '11:27:04'
result[1] // '11'
result[2] // '27'
result[3] // '04'
很方便,但这些都是“老功能”,老功能在什么情况下不好使了呢?
假设,你原来写了这么个“校验并取出时分秒”的正则表达式
但第二天,需求变了,要“校验并取出年月日时分秒”
你的新正则:/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/
.
原先,你可能在你的代码中,大量使用 result[1]
来代表“时”
这一改,就需要把 result[1]
一个一个改为 result[3]
.
哦不好意思,不是 result[3]
而是 result[4]
.
没错,不仅改得麻烦,而且非常容易出错、遗漏
命名捕获组的出现,就是为了解决这个问题:他给每个“待取出”部分,起名字
这样,就不用依赖顺序了:
let re = /(?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2})/ // 给每个“待取出”部分,加括号后,在加上 ?<name>
let result = re.exec('11:27:04')
result.groups.hour // '11'
result.groups.minute // '27'
result.groups.second // '04'
Array.prototype.flat() & Array.prototype.flatMap()
let a = [1,2,3,[4,5,[6,7]]]
let b = a.flat() // flat 接收一个参数,默认值为 1
b // [1,2,3,4,5,[6,7]]
let c = a.flat(2)
c // [1,2,3,4,5,6,7]
a // [1,2,3,[4,5,[6,7]]]
另外,a.flatMap(cb)
和a.map(cb).flat()
的效果是一样的
String.prototype.trimStart() 等
' a'.trimStart() // 'a'
' a'.trimLeft() // 'a'
'a '.trimEnd() // 'a'
'a '.trimRight() // 'a'
Object.fromEntries()
和 Object.entries
的作用是相反的:
let obj = { name: 'js', year: '2021' }
let objEntries = Object.entries(obj) // [['name', 'js'], ['year', '2021']]
let objAgain = Object.fromEntries(objEntries) // { name: 'js', year: '2021' }
obj == objAgain // false
Symbol.prototype.description
let s = Symbol('hello')
s.description // 'hello'
String.prototype.matchAll()
和 String.prototype.match
类似
区别在于返回所有匹配的结果(以 Iterator 的形式)
mdn
try-catch 写法调整
try {
throw '某种错误'
// } catch(e) { // 以前必须这样写
} catch { // 现在可以这样写
// ...
}
新的基本数据类型 BigInt
globalThis
浏览器里有一个 windows 对象
node 里有一个 global 对象
globalThis 把这俩全局变量统一了
私有成员
class A {
#name = 'js'
getName() {
this.#log()
return this.#name
}
#log() {
console.log('获取 name')
}
}
let a = new A()
a.name // undefined
a.#name // Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
a.getName() // 'js'
a.#log() // Uncaught SyntaxError: Private field '#log' must be declared in an enclosing class
(可选链操作符)?.
英文名:Optional chaining operator
个人觉得还不如叫“问号点”
以前:
function f(a) {
if(a.b && a.b.c && a.b.c.d)
return a.b.c.d.e
}
现在(或以后):
function f(a) {
return a?.b?.c?.d?.e
}
(空值合并运算符)??
英文名:Nullish coalescing operator
就叫“双问号”运算符或“俩问号”吧
以前:
function f(a) {
return a.num || 2
}
表示:如果 a.num 存在,就返回 a.num,否则返回 2
但是 a.num 为 0 或 false 时,也会返回 2
如果你只想在 a.num 为 null 或 undefined 时返回 2,就可以:
function f(a) {
return a.num ?? 2
}
静态方法
以前:
class A() {}
A.go = function() {}
module.exports = A
现在(或以后):
module.exports = class {
static go() {}
}
逻辑或赋值、逻辑且赋值
以前:
let a = null
function getA() {
if(a)
return a
a = 2
return a
}
getA() // 2
a // 2
现在(或以后):
let a = null
function getA() {
return a ||= 2
}
getA() // 2
a // 2
在单例模式中,很方便
类似的,还有 ??=
,&&=
我想你应该知道怎么用