作者:魚頭的Web海洋 公號 / 陳大魚頭 (本文轉載自前端大全)
介紹
ECMAScript是一種由Ecma國際(前身為歐洲計算機制造商協(xié)會)在標準ECMA-262中定義的腳本語言規(guī)范。這種語言在萬維網(wǎng)上應用廣泛秉氧,它往往被稱為JavaScript或JScript,但實際上后兩者是ECMA-262標準的實現(xiàn)和擴展蜒秤。
由于ES6以前的屬性誕生年底久遠汁咏,我們使用也比較普遍,遂不進行說明作媚,ES6之后的語言風格跟ES5以前的差異比較大攘滩,所以單獨拎出來做個記錄。
ES6(ES2015)
ES6是一次重大的革新纸泡,比起過去的版本漂问,改動比較大,本文僅對常用的API以及語法糖進行講解。
Let 和 Const
在ES6以前蚤假,JS只有var一種聲明方式栏饮,但是在ES6之后,就多了let跟const這兩種方式磷仰。用var定義的變量沒有塊級作用域的概念袍嬉,而let跟const則會有,因為這三個關鍵字創(chuàng)建是不一樣的灶平。
區(qū)別如下:
{
var a = 10
let b = 20
const c = 30
}
a // 10
b // Uncaught ReferenceError: b is not defined
c // c is not defined
let d = 40
const e = 50
d = 60
d // 60
e = 70 // VM231:1 Uncaught TypeError: Assignment to constant variable.
var let const
變量提升 √ × ×
全局變量 √ × ×
重復聲明 √ × ×
重新賦值 √ √ ×
暫時死區(qū) × √ √
塊作用域 × √ √
只聲明不初始化 √ √ ×
類(Class)
在ES6之前伺通,如果我們要生成一個實例對象,傳統(tǒng)的方法就是寫一個構造函數(shù)逢享,例子如下:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.information = function () {
return 'My name is ' + this.name + ', I am ' + this.age
}
但是在ES6之后罐监,我們只需要寫成以下形式:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
information() {
return 'My name is ' + this.name + ', I am ' + this.age
}
}
箭頭函數(shù)(Arrow function)
箭頭函數(shù)表達式的語法比函數(shù)表達式更簡潔,并且沒有自己的this瞒爬,arguments弓柱,super或 new.target。這些函數(shù)表達式更適用于那些本來需要匿名函數(shù)的地方疮鲫,并且它們不能用作構造函數(shù)吆你。
在ES6以前,我們寫函數(shù)一般是:
var list = [1, 2, 3, 4, 5, 6, 7]
var newList = list.map(function (item) {
return item * item
})
但是在ES6里俊犯,我們可以:
const list = [1, 2, 3, 4, 5, 6, 7]
const newList = list.map(item => item * item)
看妇多,是不是簡潔了不少
函數(shù)參數(shù)默認值(Function parameter defaults)
在ES6之前,如果我們寫函數(shù)需要定義初始值的時候燕侠,需要這么寫:
function config (data) {
var data = data || 'data is empty'
}
這樣看起來也沒有問題者祖,但是如果參數(shù)的布爾值為false時就會出問題,例如我們這樣調用config:
config(0)
config('')
那么結果就永遠是后面的值
如果我們用函數(shù)參數(shù)默認值就沒有這個問題绢彤,寫法如下:
const config = (data = 'data is empty') => {}
模板字符串(Template string)
在ES6之前七问,如果我們要拼接字符串,則需要像這樣:
var name = 'kris'
var age = 24
var info = 'My name is ' + this.name + ', I am ' + this.age
但是在ES6之后茫舶,我們只需要寫成以下形式:
const name = 'kris'
const age = 24
const info = `My name is ${name}, I am ${age}`
解構賦值(Destructuring assignment)
我們通過解構賦值, 可以將屬性/值從對象/數(shù)組中取出,賦值給其他變量械巡。
比如我們需要交換兩個變量的值,在ES6之前我們可能需要:
var a = 10
var b = 20
var temp = a
a = b
b = temp
但是在ES6里饶氏,我們有:
let a = 10
let b = 20
[a, b] = [b, a]
是不是方便很多
模塊化(Module)
在ES6之前讥耗,JS并沒有模塊化的概念,有的也只是社區(qū)定制的類似CommonJS和AMD之類的規(guī)則疹启。例如基于CommonJS的NodeJS:
// circle.js
// 輸出
const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r
// index.js
// 輸入
const circle = require('./circle.js')
console.log(`半徑為 4 的圓的面積是 ${circle.area(4)}`)
在ES6之后我們則可以寫成以下形式:
// circle.js
// 輸出
const { PI } = Math
export const area = (r) => PI * r ** 2
export const circumference = (r) => 2 * PI * r
// index.js
// 輸入
import {
area
} = './circle.js'
console.log(`半徑為 4 的圓的面積是: ${area(4)}`)
擴展操作符(Spread operator)
擴展操作符可以在函數(shù)調用/數(shù)組構造時, 將數(shù)組表達式或者string在語法層面展開古程;還可以在構造字面量對象時, 將對象表達式按key-value的方式展開。
比如在ES5的時候喊崖,我們要對一個數(shù)組的元素進行相加挣磨,在不使用reduce或者reduceRight的場合雇逞,我們需要:
function sum(x, y, z) {
return x + y + z;
}
var list = [5, 6, 7]
var total = sum.apply(null, list)
但是如果我們使用擴展操作符,只需要如下:
const sum = (x, y, z) => x + y + z
const list = [5, 6, 7]
const total = sum(...list)
非常的簡單茁裙,但是要注意的是擴展操作符只能用于可迭代對象
如果是下面的情況塘砸,是會報錯的:
var obj = {'key1': 'value1'}
var array = [...obj] // TypeError: obj is not iterable
對象屬性簡寫(Object attribute shorthand)
在ES6之前,如果我們要將某個變量賦值為同樣名稱的對象元素呜达,則需要:
var cat = 'Miaow'
var dog = 'Woof'
var bird = 'Peet peet'
var someObject = {
cat: cat,
dog: dog,
bird: bird
}
但是在ES6里我們就方便很多:
let cat = 'Miaow'
let dog = 'Woof'
let bird = 'Peet peet'
let someObject = {
cat,
dog,
bird
}
console.log(someObject)
//{
// cat: "Miaow",
// dog: "Woof",
// bird: "Peet peet"
//}
非常方便
Promise
Promise 是ES6提供的一種異步解決方案谣蠢,比回調函數(shù)更加清晰明了。
Promise 翻譯過來就是承諾的意思查近,這個承諾會在未來有一個確切的答復眉踱,并且該承諾有三種狀態(tài),分別是:
1.等待中(pending)2.完成了 (resolved)3.拒絕了(rejected)
這個承諾一旦從等待狀態(tài)變成為其他狀態(tài)就永遠不能更改狀態(tài)了霜威,也就是說一旦狀態(tài)變?yōu)?resolved 后谈喳,就不能再次改變
new Promise((resolve, reject) => {
resolve('success')
// 無效
reject('reject')
})
當我們在構造 Promise 的時候岩调,構造函數(shù)內(nèi)部的代碼是立即執(zhí)行的
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh
Promise 實現(xiàn)了鏈式調用茬末,也就是說每次調用 then 之后返回的都是一個 Promise,并且是一個全新的 Promise巴席,原因也是因為狀態(tài)不可變大猛。如果你在 then 中 使用了 return扭倾,那么 return 的值會被 Promise.resolve() 包裝
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包裝成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
當然了,Promise 也很好地解決了回調地獄的問題挽绩,例如:
ajax(url, () => {
// 處理邏輯
ajax(url1, () => {
// 處理邏輯
ajax(url2, () => {
// 處理邏輯
})
})
})
可以改寫成:
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
for...of
for...of語句在可迭代對象(包括 Array膛壹,Map,Set唉堪,String模聋,TypedArray,arguments 對象等等)上創(chuàng)建一個迭代循環(huán)唠亚,調用自定義迭代鉤子链方,并為每個不同屬性的值執(zhí)行語句。
例子如下:
const array1 = ['a', 'b', 'c'];
for (const element of array1) {
console.log(element)
}
// "a"
// "b"
// "c"
Symbol
symbol 是一種基本數(shù)據(jù)類型灶搜,Symbol()函數(shù)會返回symbol類型的值祟蚀,該類型具有靜態(tài)屬性和靜態(tài)方法。它的靜態(tài)屬性會暴露幾個內(nèi)建的成員對象割卖;它的靜態(tài)方法會暴露全局的symbol注冊前酿,且類似于內(nèi)建對象類,但作為構造函數(shù)來說它并不完整究珊,因為它不支持語法:"new Symbol()"薪者。
每個從Symbol()返回的symbol值都是唯一的纵苛。一個symbol值能作為對象屬性的標識符剿涮;這是該數(shù)據(jù)類型僅有的目的言津。
例子如下:
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1); // "symbol"
console.log(symbol3.toString()); // "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo')); // false
迭代器(Iterator)/ 生成器(Generator)
迭代器(Iterator)是一種迭代的機制,為各種不同的數(shù)據(jù)結構提供統(tǒng)一的訪問機制取试。任何數(shù)據(jù)結構只要內(nèi)部有 Iterator 接口悬槽,就可以完成依次迭代操作。
一旦創(chuàng)建瞬浓,迭代器對象可以通過重復調用next()顯式地迭代初婆,從而獲取該對象每一級的值,直到迭代完猿棉,返回{ value: undefined, done: true }
雖然自定義的迭代器是一個有用的工具磅叛,但由于需要顯式地維護其內(nèi)部狀態(tài),因此需要謹慎地創(chuàng)建萨赁。生成器函數(shù)提供了一個強大的選擇:它允許你定義一個包含自有迭代算法的函數(shù)弊琴, 同時它可以自動維護自己的狀態(tài)。生成器函數(shù)使用 function*[2]語法編寫杖爽。最初調用時敲董,生成器函數(shù)不執(zhí)行任何代碼,而是返回一種稱為Generator的迭代器慰安。通過調用生成器的下一個方法消耗值時腋寨,Generator函數(shù)將執(zhí)行,直到遇到y(tǒng)ield關鍵字化焕。
可以根據(jù)需要多次調用該函數(shù)萄窜,并且每次都返回一個新的Generator,但每個Generator只能迭代一次锣杂。
所以我們可以有以下例子:
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false}
a.next() // {value: 3, done: false}
a.next() // {value: 5, done: false}
a.next() // {value: 7, done: false}
a.next() // {value: 9, done: false}
a.next() // {value: undefined, done: true}
Set/WeakSet
Set
Set對象允許你存儲任何類型的唯一值脂倦,無論是原始值或者是對象引用。
所以我們可以通過Set實現(xiàn)數(shù)組去重
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]
WeakSet 結構與 Set 類似元莫,但區(qū)別有以下兩點:
?WeakSet 對象中只能存放對象引用, 不能存放值, 而 Set 對象都可以赖阻。?WeakSet 對象中存儲的對象值都是被弱引用的, 如果沒有其他的變量或屬性引用這個對象值, 則這個對象值會被當成垃圾回收掉. 正因為這樣, WeakSet 對象是無法被枚舉的, 沒有辦法拿到它包含的所有元素。
所以代碼如下:
var ws = new WeakSet()
var obj = {}
var foo = {}
ws.add(window)
ws.add(obj)
ws.has(window) // true
ws.has(foo) // false, 對象 foo 并沒有被添加進 ws 中
ws.delete(window) // 從集合中刪除 window 對象
ws.has(window) // false, window 對象已經(jīng)被刪除了
ws.clear() // 清空整個 WeakSet 對象
Map/WeakMap
Map 對象保存鍵值對踱蠢。任何值(對象或者原始值) 都可以作為一個鍵或一個值火欧。
例子如下,我們甚至可以使用NaN來作為鍵值:
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"
WeakMap 對象是一組鍵/值對的集合茎截,其中的鍵是弱引用的苇侵。其鍵必須是對象,而值可以是任意的榆浓。
跟Map的區(qū)別與Set跟WeakSet的區(qū)別相似,具體代碼如下:
var wm1 = new WeakMap(),
wm2 = new WeakMap(),
wm3 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value可以是任意值,包括一個對象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 鍵和值可以是任意對象,甚至另外一個WeakMap對象
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2中沒有o2這個鍵
wm2.get(o3); // undefined,值就是undefined
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是undefined)
wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
Proxy/Reflect
Proxy 對象用于定義基本操作的自定義行為(如屬性查找撕攒,賦值陡鹃,枚舉烘浦,函數(shù)調用等)。
Reflect 是一個內(nèi)置的對象萍鲸,它提供攔截 JavaScript 操作的方法闷叉。這些方法與 Proxy 的方法相同。Reflect不是一個函數(shù)對象脊阴,因此它是不可構造的握侧。
Proxy跟Reflect是非常完美的配合,例子如下:
const observe = (data, callback) => {
return new Proxy(data, {
get(target, key) {
return Reflect.get(target, key)
},
set(target, key, value, proxy) {
callback(key, value);
target[key] = value;
return Reflect.set(target, key, value, proxy)
}
})
}
const FooBar = { open: false };
const FooBarObserver = observe(FooBar, (property, value) => {
property === 'open' && value
? console.log('FooBar is open!!!')
: console.log('keep waiting');
});
console.log(FooBarObserver.open) // false
FooBarObserver.open = true // FooBar is open!!!
當然也不是什么都可以被代理的嘿期,如果對象帶有configurable: false 跟writable: false 屬性品擎,則代理失效。
Regex對象的擴展
正則新增符號
?i 修飾符
// i 修飾符
/[a-z]/i.test('\u212A') // false
/[a-z]/iu.test('\u212A') // true
?y修飾符
// y修飾符
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
?String.prototype.flags
// 查看RegExp構造函數(shù)的修飾符
var regex = new RegExp('xyz', 'i')
regex.flags // 'i'
?unicode模式
var s = '??'
/^.$/.test(s) // false
/^.$/u.test(s) // true
?u轉義
// u轉義
/\,/ // /\,/
/\,/u // 報錯 沒有u修飾符時备徐,逗號前面的反斜杠是無效的孽查,加了u修飾符就報錯。
?引用
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
字符串方法的實現(xiàn)改為調用RegExp方法
?String.prototype.match 調用 RegExp.prototype[Symbol.match]?String.prototype.replace 調用 RegExp.prototype[Symbol.replace]?String.prototype.search 調用 RegExp.prototype[Symbol.search]?String.prototype.split 調用 RegExp.prototype[Symbol.split]
正則新增屬性
?RegExp.prototype.sticky 表示是否有y修飾符
/hello\d/y.sticky // true
?RegExp.prototype.flags獲取修飾符
/abc/ig.flags // 'gi'
Math對象的擴展
?二進制表示法 : 0b或0B開頭表示二進制(0bXX或0BXX)
?二進制表示法 : 0b或0B開頭表示二進制(0bXX或0BXX)
?八進制表示法 : 0o或0O開頭表示二進制(0oXX或0OXX)
?Number.EPSILON : 數(shù)值最小精度
?Number.MIN_SAFE_INTEGER : 最小安全數(shù)值(-2^53)
?Number.MAX_SAFE_INTEGER : 最大安全數(shù)值(2^53)
?Number.parseInt() : 返回轉換值的整數(shù)部分
?Number.parseFloat() : 返回轉換值的浮點數(shù)部分
?Number.isFinite() : 是否為有限數(shù)值
?Number.isNaN() : 是否為NaN
?Number.isInteger() : 是否為整數(shù)
?Number.isSafeInteger() : 是否在數(shù)值安全范圍內(nèi)
?Math.trunc() : 返回數(shù)值整數(shù)部分
?Math.sign() : 返回數(shù)值類型(正數(shù)1坦喘、負數(shù)-1盲再、零0)
?Math.cbrt() : 返回數(shù)值立方根
?Math.clz32() : 返回數(shù)值的32位無符號整數(shù)形式
?Math.imul() : 返回兩個數(shù)值相乘
?Math.fround() : 返回數(shù)值的32位單精度浮點數(shù)形式
?Math.hypot() : 返回所有數(shù)值平方和的平方根
?Math.expm1() : 返回e^n - 1
?Math.log1p() : 返回1 + n的自然對數(shù)(Math.log(1 + n))
?Math.log10() : 返回以10為底的n的對數(shù)
?Math.log2() : 返回以2為底的n的對數(shù)
?Math.sinh() : 返回n的雙曲正弦
?Math.cosh() : 返回n的雙曲余弦
?Math.tanh() : 返回n的雙曲正切
?Math.asinh() : 返回n的反雙曲正弦
?Math.acosh() : 返回n的反雙曲余弦
?Math.atanh() : 返回n的反雙曲正切
Array對象的擴展
?Array.prototype.from:轉換具有Iterator接口的數(shù)據(jù)結構為真正數(shù)組,返回新數(shù)組瓣铣。
console.log(Array.from('foo')) // ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6]
?Array.prototype.of():轉換一組值為真正數(shù)組答朋,返回新數(shù)組。
Array.of(7) // [7]
Array.of(1, 2, 3) // [1, 2, 3]
Array(7) // [empty, empty, empty, empty, empty, empty]
Array(1, 2, 3) // [1, 2, 3]
?Array.prototype.copyWithin():把指定位置的成員復制到其他位置棠笑,返回原數(shù)組
const array1 = ['a', 'b', 'c', 'd', 'e']
console.log(array1.copyWithin(0, 3, 4)) // ["d", "b", "c", "d", "e"]
console.log(array1.copyWithin(1, 3)) // ["d", "d", "e", "d", "e"]
?Array.prototype.find():返回第一個符合條件的成員
const array1 = [5, 12, 8, 130, 44]
const found = array1.find(element => element > 10)
console.log(found) // 12
?Array.prototype.findIndex():返回第一個符合條件的成員索引值
const array1 = [5, 12, 8, 130, 44]
const isLargeNumber = (element) => element > 13
console.log(array1.findIndex(isLargeNumber)) // 3
?Array.prototype.fill():根據(jù)指定值填充整個數(shù)組梦碗,返回原數(shù)組
const array1 = [1, 2, 3, 4]
console.log(array1.fill(0, 2, 4)) // [1, 2, 0, 0]
console.log(array1.fill(5, 1)) // [1, 5, 5, 5]
console.log(array1.fill(6)) // [6, 6, 6, 6]
?Array.prototype.keys():返回以索引值為遍歷器的對象
const array1 = ['a', 'b', 'c']
const iterator = array1.keys()
for (const key of iterator) {
console.log(key)
}
// 0
// 1
// 2
?Array.prototype.values():返回以屬性值為遍歷器的對象
const array1 = ['a', 'b', 'c']
const iterator = array1.values()
for (const key of iterator) {
console.log(key)
}
// a
// b
// c
?Array.prototype.entries():返回以索引值和屬性值為遍歷器的對象
const array1 = ['a', 'b', 'c']
const iterator = array1.entries()
console.log(iterator.next().value) // [0, "a"]
console.log(iterator.next().value) // [1, "b"]
?數(shù)組空位:ES6明確將數(shù)組空位轉為undefined或者empty
Array.from(['a',,'b']) // [ "a", undefined, "b" ]
[...['a',,'b']] // [ "a", undefined, "b" ]
Array(3) // [empty × 3]
[,'a'] // [empty, "a"]
ES7(ES2016)
Array.prototype.includes()
includes() 方法用來判斷一個數(shù)組是否包含一個指定的值,根據(jù)情況蓖救,如果包含則返回 true洪规,否則返回false。
代碼如下:
const array1 = [1, 2, 3]
console.log(array1.includes(2)) // true
const pets = ['cat', 'dog', 'bat']
console.log(pets.includes('cat')) // true
console.log(pets.includes('at')) // false
冪運算符**
冪運算符**循捺,具有與Math.pow()一樣的功能斩例,代碼如下:
console.log(2**10) // 1024
console.log(Math.pow(2, 10)) // 1024
模板字符串(Template string)
自ES7起,帶標簽的模版字面量遵守以下轉義序列的規(guī)則:
?Unicode字符以"\u"開頭从橘,例如\u00A9?Unicode碼位用"\u{}"表示念赶,例如\u{2F804}?十六進制以"\x"開頭,例如\xA9?八進制以""和數(shù)字開頭恰力,例如\251
這表示類似下面這種帶標簽的模版是有問題的叉谜,因為對于每一個ECMAScript語法,解析器都會去查找有效的轉義序列踩萎,但是只能得到這是一個形式錯誤的語法:
latex`\unicode`
// 在較老的ECMAScript版本中報錯(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence
ES8(ES2017)
async/await
雖然Promise可以解決回調地獄的問題停局,但是鏈式調用太多,則會變成另一種形式的回調地獄 —— 面條地獄,所以在ES8里則出現(xiàn)了Promise的語法糖async/await董栽,專門解決這個問題履怯。
我們先看一下下面的Promise代碼:
fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message)
})
然后再看看async/await版的,這樣看起來是不是更清晰了。
async function myFetch() {
let response = await fetch('coffee.jpg')
let myBlob = await response.blob()
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
}
myFetch()
當然蝗柔,如果你喜歡闻葵,你甚至可以兩者混用
async function myFetch() {
let response = await fetch('coffee.jpg')
return await response.blob()
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
Object.values()
Object.values()方法返回一個給定對象自身的所有可枚舉屬性值的數(shù)組,值的順序與使用for...in循環(huán)的順序相同 ( 區(qū)別在于 for-in 循環(huán)枚舉原型鏈中的屬性 )。
代碼如下:
const object1 = {
a: 'somestring',
b: 42,
c: false
}
console.log(Object.values(object1)) // ["somestring", 42, false]
Object.entries()
Object.entries()方法返回一個給定對象自身可枚舉屬性的鍵值對數(shù)組胁编,其排列與使用 for...in 循環(huán)遍歷該對象時返回的順序一致(區(qū)別在于 for-in 循環(huán)還會枚舉原型鏈中的屬性)早直。
代碼如下:
const object1 = {
a: 'somestring',
b: 42
}
for (let [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`)
}
// "a: somestring"
// "b: 42"
padStart()
padStart() 方法用另一個字符串填充當前字符串(重復,如果需要的話)市框,以便產(chǎn)生的字符串達到給定的長度霞扬。填充從當前字符串的開始(左側)應用的。
代碼如下:
const str1 = '5'
console.log(str1.padStart(2, '0')) // "05"
const fullNumber = '2034399002125581'
const last4Digits = fullNumber.slice(-4)
const maskedNumber = last4Digits.padStart(fullNumber.length, '*')
console.log(maskedNumber) // "************5581"
padEnd()
padEnd() 方法會用一個字符串填充當前字符串(如果需要的話則重復填充)枫振,返回填充后達到指定長度的字符串喻圃。從當前字符串的末尾(右側)開始填充。
const str1 = 'Breaded Mushrooms'
console.log(str1.padEnd(25, '.')) // "Breaded Mushrooms........"
const str2 = '200'
console.log(str2.padEnd(5)) // "200 "
函數(shù)參數(shù)結尾逗號(Function parameter lists and calls trailing commas)
在ES5里就添加了對象的尾逗號粪滤,不過并不支持函數(shù)參數(shù)斧拍,但是在ES8之后,便開始支持這一特性杖小,代碼如下:
// 參數(shù)定義
function f(p) {}
function f(p,) {}
(p) => {}
(p,) => {}
class C {
one(a,) {},
two(a, b,) {},
}
var obj = {
one(a,) {},
two(a, b,) {},
};
// 函數(shù)調用
f(p)
f(p,)
Math.max(10, 20)
Math.max(10, 20,)
但是以下的方式是不合法的:
僅僅包含逗號的函數(shù)參數(shù)定義或者函數(shù)調用會拋出 SyntaxError饮焦。而且,當使用剩余參數(shù)的時候窍侧,并不支持尾后逗號县踢,例子如下:
function f(,) {} // SyntaxError: missing formal parameter
(,) => {} // SyntaxError: expected expression, got ','
f(,) // SyntaxError: expected expression, got ','
function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {} // SyntaxError: expected closing parenthesis, got ','
在解構里也可以使用,代碼如下:
// 帶有尾后逗號的數(shù)組解構
[a, b,] = [1, 2]
// 帶有尾后逗號的對象解構
var o = {
p: 42,
q: true,
}
var {p, q,} = o
同樣地伟件,在使用剩余參數(shù)時硼啤,會拋出 SyntaxError,代碼如下:
var [a, ...b,] = [1, 2, 3] // SyntaxError: rest element may not have a trailing comma
ShareArrayBuffer(因安全問題斧账,暫時在Chrome跟FireFox中被禁用)
SharedArrayBuffer 對象用來表示一個通用的谴返,固定長度的原始二進制數(shù)據(jù)緩沖區(qū)煞肾,類似于 ArrayBuffer 對象,它們都可以用來在共享內(nèi)存(shared memory)上創(chuàng)建視圖嗓袱。與 ArrayBuffer 不同的是籍救,SharedArrayBuffer 不能被分離。
代碼如下:
let sab = new SharedArrayBuffer(1024) // 必須實例化
worker.postMessage(sab)
Atomics對象
Atomics對象 提供了一組靜態(tài)方法用來對 SharedArrayBuffer[3] 對象進行原子操作渠抹。
方法如下:
?Atomics.add() :將指定位置上的數(shù)組元素與給定的值相加蝙昙,并返回相加前該元素的值。
?Atomics.and():將指定位置上的數(shù)組元素與給定的值相與梧却,并返回與操作前該元素的值奇颠。
?Atomics.compareExchange():如果數(shù)組中指定的元素與給定的值相等,則將其更新為新的值放航,并返回該元素原先的值烈拒。
?Atomics.exchange():將數(shù)組中指定的元素更新為給定的值,并返回該元素更新前的值广鳍。
?Atomics.load():返回數(shù)組中指定元素的值荆几。
?Atomics.or():將指定位置上的數(shù)組元素與給定的值相或,并返回或操作前該元素的值赊时。
?Atomics.store():將數(shù)組中指定的元素設置為給定的值伴郁,并返回該值。
?Atomics.sub():將指定位置上的數(shù)組元素與給定的值相減蛋叼,并返回相減前該元素的值焊傅。
?Atomics.xor():將指定位置上的數(shù)組元素與給定的值相異或,并返回異或操作前該元素的值狈涮。
?Atomics.wait():檢測數(shù)組中某個指定位置上的值是否仍然是給定值狐胎,是則保持掛起直到被喚醒或超時。返回值為 "ok"歌馍、"not-equal" 或 "time-out"握巢。調用時,如果當前線程不允許阻塞松却,則會拋出異常(大多數(shù)瀏覽器都不允許在主線程中調用 wait())暴浦。
?Atomics.wake():喚醒等待隊列中正在數(shù)組指定位置的元素上等待的線程。返回值為成功喚醒的線程數(shù)量晓锻。
?Atomics.isLockFree(size):可以用來檢測當前系統(tǒng)是否支持硬件級的原子操作歌焦。對于指定大小的數(shù)組,如果當前系統(tǒng)支持硬件級的原子操作砚哆,則返回 true独撇;否則就意味著對于該數(shù)組,Atomics 對象中的各原子操作都只能用鎖來實現(xiàn)。此函數(shù)面向的是技術專家纷铣。
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors() 方法用來獲取一個對象的所有自身屬性的描述符卵史。代碼如下:
const object1 = {
property1: 42
}
const descriptors1 = Object.getOwnPropertyDescriptors(object1)
console.log(descriptors1.property1.writable) // true
console.log(descriptors1.property1.value) // 42
// 淺拷貝一個對象
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
// 創(chuàng)建子類
function superclass() {}
superclass.prototype = {
// 在這里定義方法和屬性
}
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
// 在這里定義方法和屬性
}))
ES9(ES2018)
for await...of
for await...of 語句會在異步或者同步可迭代對象上創(chuàng)建一個迭代循環(huán),包括 String搜立,Array以躯,Array-like 對象(比如arguments 或者NodeList),TypedArray啄踊,Map忧设, Set和自定義的異步或者同步可迭代對象。其會調用自定義迭代鉤子社痛,并為每個不同屬性的值執(zhí)行語句。
配合迭代異步生成器命雀,例子如下:
async function* asyncGenerator() {
var i = 0
while (i < 3) {
yield i++
}
}
(async function() {
for await (num of asyncGenerator()) {
console.log(num)
}
})()
// 0
// 1
// 2
模板字符串(Template string)
ES9開始蒜哀,模板字符串允許嵌套支持常見轉義序列,移除對ECMAScript在帶標簽的模版字符串中轉義序列的語法限制吏砂。
不過撵儿,非法轉義序列在"cooked"當中仍然會體現(xiàn)出來。它們將以undefined元素的形式存在于"cooked"之中狐血,代碼如下:
function latex(str) {
return { "cooked": str[0], "raw": str.raw[0] }
}
latex`\unicode` // { cooked: undefined, raw: "\\unicode" }
正則表達式反向(lookbehind)斷言
首先我們得先知道什么是斷言(Assertion)淀歇。
斷言(Assertion)是一個對當前匹配位置之前或之后的字符的測試, 它不會實際消耗任何字符匈织,所以斷言也被稱為“非消耗性匹配”或“非獲取匹配”浪默。
正則表達式的斷言一共有 4 種形式:
?(?=pattern) 零寬正向肯定斷言(zero-width positive lookahead assertion)?(?!pattern) 零寬正向否定斷言(zero-width negative lookahead assertion)?(?<=pattern) 零寬反向肯定斷言(zero-width positive lookbehind assertion)?(?<!pattern) 零寬反向否定斷言(zero-width negative lookbehind assertion)
在ES9之前,JavaScript 正則表達式缀匕,只支持正向斷言纳决。正向斷言的意思是:當前位置后面的字符串應該滿足斷言,但是并不捕獲乡小。例子如下:
'fishHeadfishTail'.match(/fish(?=Head)/g) // ["fish"]
反向斷言和正向斷言的行為一樣阔加,只是方向相反。例子如下:
'abc123'.match(/(?<=(\d+)(\d+))$/) // ["", "1", "23", index: 6, input: "abc123", groups: undefined]
正則表達式 Unicode 轉義
正則表達式中的Unicode轉義符允許根據(jù)Unicode字符屬性匹配Unicode字符满钟。它允許區(qū)分字符類型胜榔,例如大寫和小寫字母,數(shù)學符號和標點符號湃番。
部分例子代碼如下:
// 匹配所有數(shù)字
const regex = /^\p{Number}+$/u;
regex.test('231???') // true
regex.test('???') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
// 匹配所有空格
\p{White_Space}
// 匹配各種文字的所有字母夭织,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配各種文字的所有非字母的字符矾策,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu
// 匹配所有的箭頭字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓??↖↗↘↙?????????????') // true
具體的屬性列表可查看:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
正則表達式 s/dotAll 模式
在以往的版本里躲履,JS的正則的.只能匹配emoji跟行終結符以外的所有文本隅很,例如:
let regex = /./;
regex.test('\n'); // false
regex.test('\r'); // false
regex.test('\u{2028}'); // false
regex.test('\u{2029}'); // false
regex.test('\v'); // true
regex.test('\f'); // true
regex.test('\u{0085}'); // true
/foo.bar/.test('foo\nbar'); // false
/foo[^]bar/.test('foo\nbar'); // true
/foo.bar/.test('foo\nbar'); // false
/foo[\s]bar/.test('foo\nbar'); // true
但是在ES9之后,JS正則增加了一個新的標志 s 用來表示 dotAll黍翎,這可以匹配任意字符。代碼如下:
/foo.bar/s.test('foo\nbar'); // true
const re = /foo.bar/s; // 等價于 const re = new RegExp('foo.bar', 's');
re.test('foo\nbar'); // true
re.dotAll; // true
re.flags; // "s"
正則表達式命名捕獲組
在以往的版本里追城,JS的正則分組是無法命名的捣域,所以容易混淆。例如下面獲取年月日的例子琢岩,很容易讓人搞不清哪個是月份投剥,哪個是年份:
const matched = /(\d{4})-(\d{2})-(\d{2})/.exec('2019-01-01')
console.log(matched[0]); // 2019-01-01
console.log(matched[1]); // 2019
console.log(matched[2]); // 01
console.log(matched[3]); // 01
ES9引入了命名捕獲組,允許為每一個組匹配指定一個名字担孔,既便于閱讀代碼江锨,又便于引用。代碼如下:
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
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
matchObj.groups.as // undefined
'as' in matchObj.groups // true
對象擴展操作符
ES6中添加了數(shù)組的擴展操作符糕篇,讓我們在操作數(shù)組時更加簡便啄育,美中不足的是并不支持對象擴展操作符,但是在ES9開始拌消,這一功能也得到了支持挑豌,例如:
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的對象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的對象: { foo: "baz", x: 42, y: 13 }
上面便是一個簡便的淺拷貝。這里有一點小提示墩崩,就是Object.assign() 函數(shù)會觸發(fā) setters氓英,而展開語法則不會。所以不能替換也不能模擬Object.assign() 鹦筹。
如果存在相同的屬性名铝阐,只有最后一個會生效。
Promise.prototype.finally()
finally()方法會返回一個Promise铐拐,當promise的狀態(tài)變更徘键,不管是變成rejected或者fulfilled,最終都會執(zhí)行finally()的回調遍蟋。
例子如下:
fetch(url)
.then((res) => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
.finally(() => {
console.log('結束')
})
ES10(ES2019)
Array.prototype.flat() / flatMap()
flat() 方法會按照一個可指定的深度遞歸遍歷數(shù)組啊鸭,并將所有元素與遍歷到的子數(shù)組中的元素合并為一個新數(shù)組返回。
flatMap()與 map() 方法和深度為1的 flat() 幾乎相同.匿值,不過它會首先使用映射函數(shù)映射每個元素赠制,然后將結果壓縮成一個新數(shù)組,這樣效率會更高挟憔。
例子如下:
var arr1 = [1, 2, 3, 4]
arr1.map(x => [x * 2]) // [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]) // [2, 4, 6, 8]
// 深度為1
arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
flatMap()可以代替reduce() 與 concat()钟些,例子如下:
var arr = [1, 2, 3, 4]
arr.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8]
// 等價于
arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]
但這是非常低效的,在每次迭代中绊谭,它創(chuàng)建一個必須被垃圾收集的新臨時數(shù)組政恍,并且它將元素從當前的累加器數(shù)組復制到一個新的數(shù)組中,而不是將新的元素添加到現(xiàn)有的數(shù)組中达传。
String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()
在ES5中篙耗,我們可以通過trim()來去掉字符首尾的空格迫筑,但是卻無法只去掉單邊的,但是在ES10之后宗弯,我們可以實現(xiàn)這個功能脯燃。
如果我們要去掉開頭的空格,可以使用trimStart()或者它的別名trimLeft()蒙保,
同樣的辕棚,如果我們要去掉結尾的空格,我們可以使用trimEnd()或者它的別名trimRight()邓厕。
例子如下:
const Str = ' Hello world! '
console.log(Str) // ' Hello world! '
console.log(Str.trimStart()) // 'Hello world! '
console.log(Str.trimLeft()) // 'Hello world! '
console.log(Str.trimEnd()) // ' Hello world!'
console.log(Str.trimRight()) // ' Hello world!'
不過這里有一點要注意的是逝嚎,trimStart()跟trimEnd()才是標準方法,trimLeft()跟trimRight()只是別名详恼。
在某些引擎里(例如Chrome)补君,有以下的等式:
String.prototype.trimLeft.name === "trimStart"
String.prototype.trimRight.name === "trimEnd"
Object.fromEntries()
Object.fromEntries() 方法把鍵值對列表轉換為一個對象,它是Object.entries()的反函數(shù)昧互。
例子如下:
const entries = new Map([
['foo', 'bar'],
['baz', 42]
])
const obj = Object.fromEntries(entries)
console.log(obj) // Object { foo: "bar", baz: 42 }
Symbol.prototype.description
description 是一個只讀屬性挽铁,它會返回Symbol對象的可選描述的字符串。與 Symbol.prototype.toString() 不同的是它不會包含Symbol()的字符串硅堆。例子如下:
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined
// 具名 symbols
Symbol.iterator.toString(); // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"
//全局 symbols
Symbol.for('foo').toString(); // "Symbol(foo)"
Symbol.for('foo').description; // "foo"
String.prototype.matchAll
matchAll() 方法返回一個包含所有匹配正則表達式的結果及分組捕獲組的迭代器屿储。并且返回一個不可重啟的迭代器贿讹。例子如下:
var regexp = /t(e)(st(\d?))/g
var str = 'test1test2'
str.match(regexp) // ['test1', 'test2']
str.matchAll(regexp) // RegExpStringIterator {}
[...str.matchAll(regexp)] // [['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4], ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]]
Function.prototype.toString() 返回注釋與空格
在以往的版本中渐逃,F(xiàn)unction.prototype.toString()得到的字符串是去掉空白符號的,但是從ES10開始會保留這些空格民褂,如果是原生函數(shù)則返回你控制臺看到的效果茄菊,例子如下:
function sum(a, b) {
return a + b;
}
console.log(sum.toString())
// "function sum(a, b) {
// return a + b;
// }"
console.log(Math.abs.toString()) // "function abs() { [native code] }"
try-catch
在以往的版本中,try-catch里catch后面必須帶異常參數(shù)赊堪,例如:
// ES10之前
try {
// tryCode
} catch (err) {
// catchCode
}
但是在ES10之后面殖,這個參數(shù)卻不是必須的,如果用不到哭廉,我們可以不用傳脊僚,例如:
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
BigInt
BigInt 是一種內(nèi)置對象,它提供了一種方法來表示大于 253 - 1 的整數(shù)遵绰。這原本是 Javascript中可以用 Number 表示的最大數(shù)字辽幌。BigInt 可以表示任意大的整數(shù)。
可以用在一個整數(shù)字面量后面加 n 的方式定義一個 BigInt 椿访,如:10n乌企,或者調用函數(shù)BigInt()。
在以往的版本中成玫,我們有以下的弊端:
// 大于2的53次方的整數(shù)加酵,無法保持精度
2 ** 53 === (2 ** 53 + 1)
// 超過2的1024次方的數(shù)值拳喻,無法表示
2 ** 1024 // Infinity
但是在ES10引入BigInt之后,這個問題便得到了解決猪腕。
以下操作符可以和 BigInt 一起使用: +冗澈、、-码撰、*渗柿、% 。除 >>> (無符號右移)之外的位操作也可以支持脖岛。因為 BigInt 都是有符號的朵栖, >>> (無符號右移)不能用于 BigInt。BigInt 不支持單目 (+) 運算符柴梆。
/ 操作符對于整數(shù)的運算也沒問題陨溅。可是因為這些變量是 BigInt 而不是 BigDecimal 绍在,該操作符結果會向零取整门扇,也就是說不會返回小數(shù)部分。
BigInt 和 Number不是嚴格相等的偿渡,但是寬松相等的臼寄。
所以在BigInt出來以后,JS的原始類型便增加到了7個溜宽,如下:
?Boolean?Null?Undefined?Number?String?Symbol (ES6)?BigInt (ES10)
globalThis
globalThis屬性包含類似于全局對象 this值吉拳。所以在全局環(huán)境下,我們有:
globalThis === this // true
import()
靜態(tài)的import 語句用于導入由另一個模塊導出的綁定适揉。無論是否聲明了 嚴格模式留攒,導入的模塊都運行在嚴格模式下。在瀏覽器中嫉嘀,import 語句只能在聲明了 type="module" 的 script 的標簽中使用炼邀。
但是在ES10之后,我們有動態(tài) import()剪侮,它不需要依賴 type="module" 的script標簽拭宁。
所以我們有以下例子:
const main = document.querySelector("main")
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
e.preventDefault()
import('/modules/my-module.js')
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
})
})
}
私有元素與方法
在ES10之前,如果我們要實現(xiàn)一個簡單的計數(shù)器組件瓣俯,我們可能會這么寫:
// web component 寫法
class Counter extends HTMLElement {
get x() {
return this.xValue
}
set x(value) {
this.xValue = value
window.requestAnimationFrame(this.render.bind(this))
}
clicked() {
this.x++
}
constructor() {
super()
this.onclick = this.clicked.bind(this)
this.xValue = 0
}
connectedCallback() {
this.render()
}
render() {
this.textContent = this.x.toString()
}
}
window.customElements.define('num-counter', Counter)
但是在ES10之后我們可以使用私有變量進行組件封裝杰标,如下:
class Counter extends HTMLElement {
#xValue = 0
get #x() {
return #xValue
}
set #x(value) {
this.#xValue = value
window.requestAnimationFrame(this.#render.bind(this))
}
#clicked() {
this.#x++
}
constructor() {
super();
this.onclick = this.#clicked.bind(this)
}
connectedCallback() {
this.#render()
}
#render() {
this.textContent = this.#x.toString()
}
}
window.customElements.define('num-counter', Counter)