三除盏,字符串?dāng)U展
3.1 Unicode表示法
ES6 做出了改進(jìn)噩斟,只要將碼點(diǎn)放入大括號阳欲,就能正確解讀該字符巴元。
有了這種表示法之后毡咏,JavaScript 共有6種方法可以表示一個字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
3.2 codePointAt()
這篇文章講的特別好N衩帷血当!解釋了什么事unicode,utf-8,utf-16以及javascript的實(shí)現(xiàn)。
對于四字節(jié)字符,charAt方法無法讀取整個字符臊旭,charCodeAt方法只能分別返回前兩個字節(jié)和后兩個字節(jié)的值落恼。ES6提供了codePointAt方法,能夠正確處理4個字節(jié)儲存的字符离熏,返回一個字符的碼點(diǎn)佳谦。
let s = '??a';
s.codePointAt(0) // 134071
codePointAt方法的參數(shù),仍然是不正確的滋戳。比如钻蔑,上面代碼中,字符a在字符串s的正確位置序號應(yīng)該是1奸鸯,但是必須向codePointAt方法傳入2咪笑。解決這個問題的一個辦法是使用for...of循環(huán),因?yàn)樗鼤_識別32位的UTF-16字符娄涩。
codePointAt方法是測試一個字符由兩個字節(jié)還是由四個字節(jié)組成的最簡單方法窗怒。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
3.3 String.fromCodePoint()
ES6提供了String.fromCodePoint方法,可以識別大于0xFFFF的字符蓄拣,彌補(bǔ)了String.fromCharCode方法的不足扬虚。在作用上,正好與codePointAt方法相反球恤。注意辜昵,fromCodePoint方法定義在String對象上,而codePointAt方法定義在字符串的實(shí)例對象上咽斧。
String.fromCodePoint(0x20BB7)
// "??"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
上面代碼中堪置,如果String.fromCodePoint方法有多個參數(shù),則它們會被合并成一個字符串返回收厨。
3.4 字符串遍歷接口
字符串可以被for...of循環(huán)遍歷晋柱。最大的優(yōu)點(diǎn)是可以識別大于0xFFFF的碼點(diǎn),傳統(tǒng)的for循環(huán)無法識別這樣的碼點(diǎn)诵叁。
3.5 at()
目前,有一個提案钦椭,提出字符串實(shí)例的at方法拧额,可以識別 Unicode 編號大于0xFFFF的字符,返回正確的字符彪腔。ES5的charAt方法有局限侥锦。這個方法可以通過墊片庫實(shí)現(xiàn)。
3.6 normalize()
ES6 提供字符串實(shí)例的normalize()方法德挣,用來將字符的不同表示方法統(tǒng)一為同樣的形式恭垦,這稱為 Unicode 正規(guī)化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true
3.7 includes(), startsWith(), endsWith()
傳統(tǒng)上,JavaScript只有indexOf方法番挺,可以用來確定一個字符串是否包含在另一個字符串中唠帝。ES6又提供了三種新方法。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代碼表示玄柏,使用第二個參數(shù)n時襟衰,endsWith的行為與其他兩個方法有所不同。它針對前n個字符粪摘,而其他兩個方法針對從第n個位置直到字符串結(jié)束瀑晒。
3.8 repeat()
repeat方法返回一個新字符串,表示將原字符串重復(fù)n次徘意。
'x'.repeat(3) // "xxx"
3.9 padStart()苔悦,padEnd()
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
上面代碼中,padStart和padEnd一共接受兩個參數(shù)椎咧,第一個參數(shù)用來指定字符串的長度间坐,第二個參數(shù)是用來補(bǔ)全的字符串。
3.10 模板字符串(template string)
模板字符串是增強(qiáng)版的字符串邑退,用反引號(`)標(biāo)識竹宋。它可以當(dāng)作普通字符串使用,也可以用來定義多行字符串地技,或者在字符串中嵌入變量蜈七。如果使用模板字符串表示多行字符串,所有的空格和縮進(jìn)都會被保留在輸出之中莫矗。
// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
模板字符串甚至還能嵌套飒硅。
3.11 實(shí)例:模板編譯
這里是模板字符串的一個例子。
通過模板字符串作谚,生成正式模板的實(shí)例三娩。具體代碼看教程。
3.12 標(biāo)簽?zāi)0澹╰agged template)
模板字符串的功能妹懒,不僅僅是上面這些雀监。它可以緊跟在一個函數(shù)名后面,該函數(shù)將被調(diào)用來處理這個模板字符串眨唬。這被稱為“標(biāo)簽?zāi)0濉被崆啊?biāo)簽?zāi)0迤鋵?shí)不是模板,而是函數(shù)調(diào)用的一種特殊形式匾竿⊥咭耍“標(biāo)簽”指的就是函數(shù),緊跟在后面的模板字符串就是它的參數(shù)岭妖。
alert`123`
// 等同于
alert(123)
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
“標(biāo)簽?zāi)0濉钡囊粋€重要應(yīng)用临庇,就是過濾 HTML 字符串反璃,防止用戶輸入惡意內(nèi)容。
標(biāo)簽?zāi)0宓牧硪粋€應(yīng)用假夺,就是多語言轉(zhuǎn)換(國際化處理)淮蜈。模板字符串本身并不能取代Mustache之類的模板庫,因?yàn)闆]有條件判斷和循環(huán)處理功能侄泽,但是通過標(biāo)簽函數(shù)礁芦,你可以自己添加這些功能。
模板處理函數(shù)的第一個參數(shù)(模板字符串?dāng)?shù)組)悼尾,還有一個raw屬性柿扣。
3.13 string.raw
ES6還為原生的String對象,提供了一個raw方法闺魏。
String.raw方法未状,往往用來充當(dāng)模板字符串的處理函數(shù),返回一個斜杠都被轉(zhuǎn)義(即斜杠前面再加一個斜杠)的字符串析桥,對應(yīng)于替換變量后的模板字符串司草。
String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"
3.14 模板字符串的限制
前面提到標(biāo)簽?zāi)0謇锩妫梢詢?nèi)嵌其他語言。但是,模板字符串默認(rèn)會將字符串轉(zhuǎn)義盼玄,導(dǎo)致無法嵌入其他語言。為了解決這個問題搔课,現(xiàn)在有一個提案,放松對標(biāo)簽?zāi)0謇锩娴淖址D(zhuǎn)義的限制截亦。如果遇到不合法的字符串轉(zhuǎn)義爬泥,就返回undefined,而不是報(bào)錯崩瓤,并且從raw屬性上面可以得到原始字符串袍啡。注意,這種對字符串轉(zhuǎn)義的放松却桶,只在標(biāo)簽?zāi)0褰馕鲎址畷r生效境输,不是標(biāo)簽?zāi)0宓膱龊希廊粫?bào)錯肾扰。
四畴嘶,正則的擴(kuò)展
4.1 RegExp 構(gòu)造函數(shù)
在 ES5 中,RegExp構(gòu)造函數(shù)的參數(shù)有兩種情況集晚。第一種情況是,參數(shù)是字符串区匣,這時第二個參數(shù)表示正則表達(dá)式的修飾符(flag)偷拔; 第二種情況是蒋院,參數(shù)是一個正則表示式,這時會返回一個原有正則表達(dá)式的拷貝莲绰,但不允許此時使用第二個參數(shù)添加修飾符欺旧,否則會報(bào)錯。
ES6 改變了蛤签。如果RegExp第一個參數(shù)是一個正則對象辞友,那么可以使用第二個參數(shù)指定修飾符。而且震肮,返回的正則表達(dá)式會忽略原有的正則表達(dá)式的修飾符称龙,只使用新指定的修飾符。
4.2 字符串的正則方法
4.3 u 修飾符
ES6 對正則表達(dá)式添加了u修飾符戳晌,含義為“Unicode模式”鲫尊,用來正確處理大于\uFFFF的 Unicode 字符。一旦加上u修飾符號沦偎,就會修改下面這些正則表達(dá)式的行為疫向。
var s = '??';
/^.$/.test(s) // false
/^.$/u.test(s) // true
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/??{2}/.test('????') // false
/??{2}/u.test('????') // true
利用這一點(diǎn),可以寫出一個正確返回字符串長度的函數(shù)豪嚎。
function codePointLength(text) {
var result = text.match(/[\s\S]/gu);
return result ? result.length : 0;
}
還可以用spread判斷:
function length(str) {
return [...str].length;
}
4.4 y 修飾符
ES6 還為正則表達(dá)式添加了y修飾符搔驼,叫做“粘連”(sticky)修飾符。
y修飾符的作用與g修飾符類似侈询,也是全局匹配舌涨,后一次匹配都從上一次匹配成功的下一個位置開始。不同之處在于妄荔,g修飾符只要剩余位置中存在匹配就可泼菌,而y修飾符確保匹配必須從剩余的第一個位置開始,這也就是“粘連”的涵義啦租。
y修飾符的設(shè)計(jì)本意哗伯,就是讓頭部匹配的標(biāo)志^在全局匹配中都有效。
4.5 sticky 屬性
與y修飾符相匹配篷角,ES6 的正則對象多了sticky屬性焊刹,表示是否設(shè)置了y修飾符。
var r = /hello\d/y;
r.sticky // true
4.6 flags 屬性
ES6 為正則表達(dá)式新增了flags屬性恳蹲,會返回正則表達(dá)式的修飾符虐块。
// ES5 的 source 屬性
// 返回正則表達(dá)式的正文
/abc/ig.source
// "abc"
// ES6 的 flags 屬性
// 返回正則表達(dá)式的修飾符
/abc/ig.flags
// 'gi'
4.7 s 修飾符
點(diǎn)(.)是一個特殊字符,代表任意的單個字符嘉蕾,但是行終止符(line terminator character)除外贺奠。但是,很多時候我們希望匹配的是任意單個字符错忱,這時有一種變通的寫法儡率。/foo[^]bar/.test('foo\nbar') // true
挂据。
這種解決方案畢竟不太符合直覺,所以現(xiàn)在有一個提案儿普,引入/s
修飾符崎逃,使得.
可以匹配任意單個字符。/foo.bar/s.test('foo\nbar') // true
眉孩。
這被稱為dotAll模式个绍,即點(diǎn)(dot)代表一切字符。所以浪汪,正則表達(dá)式還引入了一個dotAll屬性巴柿,返回一個布爾值,表示該正則表達(dá)式是否處在dotAll模式吟宦。
4.8 后行斷言
JavaScript 語言的正則表達(dá)式篮洁,只支持先行斷言(lookahead)和先行否定斷言(negative lookahead),不支持后行斷言(lookbehind)和后行否定斷言(negative lookbehind)殃姓。目前袁波,有一個提案,引入后行斷言蜗侈,V8 引擎4.9版已經(jīng)支持篷牌。
4.9 Unicode 屬性類
目前,有一個提案踏幻,引入了一種新的類的寫法\p{...}
和\P{...}
枷颊,允許正則表達(dá)式匹配符合 Unicode 某種屬性的所有字符。
4.10 具名組匹配
正則表達(dá)式使用圓括號進(jìn)行組匹配该面。const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
上面代碼中夭苗,正則表達(dá)式里面有三組圓括號。使用exec方法隔缀,就可以將這三組匹配結(jié)果提取出來题造。
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
組匹配的一個問題是,每一組的匹配含義不容易看出來猾瘸,而且只能用數(shù)字序號引用界赔,要是組的順序變了,引用的時候就必須修改序號牵触。
現(xiàn)在有一個“具名組匹配”(Named Capture Groups)的提案淮悼,允許為每一個組匹配指定一個名字,既便于閱讀代碼揽思,又便于引用袜腥。
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
如果具名組沒有匹配,那么對應(yīng)的groups對象屬性會是undefined钉汗。
解構(gòu)賦值和替換
有了具名組匹配以后瞧挤,可以使用解構(gòu)賦值直接從匹配結(jié)果上為變量賦值锡宋。
引用
如果要在正則表達(dá)式內(nèi)部引用某個“具名組匹配”儡湾,可以使用\k<組名>的寫法特恬。數(shù)字引用(\1)依然有效。
五徐钠,數(shù)值的擴(kuò)展
5.1 二進(jìn)制和八進(jìn)制表示法
ES6 提供了二進(jìn)制和八進(jìn)制數(shù)值的新的寫法癌刽,分別用前綴0b(或0B)和0o(或0O)表示。從 ES5 開始尝丐,在嚴(yán)格模式之中显拜,八進(jìn)制就不再允許使用前綴0表示,ES6 進(jìn)一步明確爹袁,要使用前綴0o表示远荠。ES5中沒有二進(jìn)制的定義。
5.2 Number.isFinite(), Number.isNaN()
ES6 在Number對象上失息,新提供了Number.isFinite()和Number.isNaN()兩個方法譬淳。ES5中有g(shù)lobal的傳統(tǒng)的全局方法isFinite()和isNaN()。
區(qū)別在于盹兢,傳統(tǒng)方法先調(diào)用Number()將非數(shù)值的值轉(zhuǎn)為數(shù)值邻梆,再進(jìn)行判斷,而這兩個新方法只對數(shù)值有效绎秒,Number.isFinite()對于非數(shù)值一律返回false, Number.isNaN()只有對于NaN才返回true浦妄,非NaN一律返回false。
5.3 Number.parseInt(), Number.parseFloat()
ES6 將全局方法parseInt()和parseFloat()见芹,移植到Number對象上面剂娄,行為完全保持不變。這樣做的目的玄呛,是逐步減少全局性方法阅懦,使得語言逐步模塊化。
5.4 Number.isInteger()
Number.isInteger()用來判斷一個值是否為整數(shù)把鉴。需要注意的是故黑,在 JavaScript 內(nèi)部,整數(shù)和浮點(diǎn)數(shù)是同樣的儲存方法庭砍,所以3和3.0被視為同一個值场晶。
5.5 Number.EPSILON
ES6 在Number對象上面,新增一個極小的常量Number.EPSILON怠缸。根據(jù)規(guī)格诗轻,它表示1與大于1的最小浮點(diǎn)數(shù)之間的差。
Number.EPSILON可以用來設(shè)置“能夠接受的誤差范圍”揭北。比如扳炬,誤差范圍設(shè)為2的-50次方(即Number.EPSILON * Math.pow(2, 2))吏颖,即如果兩個浮點(diǎn)數(shù)的差小于這個值,我們就認(rèn)為這兩個浮點(diǎn)數(shù)相等恨樟。
5.6 安全整數(shù)和 Number.isSafeInteger()
JavaScript 能夠準(zhǔn)確表示的整數(shù)范圍在-253到253之間(不含兩個端點(diǎn))半醉,超過這個范圍,無法精確表示這個值劝术。ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER這兩個常量缩多,用來表示這個范圍的上下限。
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
// true
Number.MAX_SAFE_INTEGER === 9007199254740991
// true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
// true
Number.MIN_SAFE_INTEGER === -9007199254740991
// true
Number.isSafeInteger()則是用來判斷一個整數(shù)是否落在這個范圍之內(nèi)养晋。實(shí)際使用這個函數(shù)時衬吆,需要注意。驗(yàn)證運(yùn)算結(jié)果是否落在安全整數(shù)的范圍內(nèi)绳泉,不要只驗(yàn)證運(yùn)算結(jié)果逊抡,而要同時驗(yàn)證參與運(yùn)算的每個值。
5.7 Math對象的擴(kuò)展
Math.trunc() Math.sign() Math.cbrt() Math.clz32() Math.imul() Math.fround() Math.hypot()
對數(shù)方法(1) Math.expm1()(2)Math.log1p()(3)Math.log10()(4)Math.log2()
ES6新增了6個雙曲函數(shù)方法零酪。Math.sinh(x)等
5.8 Math.signbit()
目前冒嫡,有一個提案,引入了Math.signbit() 方法判斷一個數(shù)的符號位是否設(shè)置了蛾娶。
5.9 指數(shù)運(yùn)算符
ES2016 新增了一個指數(shù)運(yùn)算符()灯谣。指數(shù)運(yùn)算符可以與等號結(jié)合,形成一個新的賦值運(yùn)算符(=)蛔琅。注意胎许,在 V8 引擎中,指數(shù)運(yùn)算符與Math.pow的實(shí)現(xiàn)不相同罗售,對于特別大的運(yùn)算結(jié)果辜窑,兩者會有細(xì)微的差異。
5.10 Integer 數(shù)據(jù)類型
JavaScript 所有數(shù)字都保存成64位浮點(diǎn)數(shù)寨躁,這決定了整數(shù)的精確程度只能到53個二進(jìn)制位穆碎。大于這個范圍的整數(shù),JavaScript 是無法精確表示的职恳。
現(xiàn)在有一個提案所禀,引入了新的數(shù)據(jù)類型 Integer(整數(shù)),來解決這個問題放钦。整數(shù)類型的數(shù)據(jù)只用來表示整數(shù)色徘,沒有位數(shù)的限制,任何位數(shù)的整數(shù)都可以精確表示操禀。
1n + 2n // 3n
0b1101n // 二進(jìn)制
0o777n // 八進(jìn)制
0xFFn // 十六進(jìn)制
Integer 類型不能與 Number 類型進(jìn)行混合運(yùn)算褂策。相等運(yùn)算符(==)會改變數(shù)據(jù)類型,也是不允許混合使用。精確相等運(yùn)算符(===)不會改變數(shù)據(jù)類型斤寂,因此可以混合使用耿焊。
幾乎所有的 Number 運(yùn)算符都可以用在 Integer,但是有兩個除外:不帶符號的右移位運(yùn)算符>>>和一元的求正運(yùn)算符+遍搞,使用時會報(bào)錯罗侯。
六,函數(shù)的擴(kuò)展
6.1 函數(shù)參數(shù)的默認(rèn)值
ES6 之前尾抑,不能直接為函數(shù)的參數(shù)指定默認(rèn)值歇父,只能采用變通的方法。為了避免參數(shù)y賦值了但是對應(yīng)的布爾值為false的問題再愈,通常需要先判斷一下參數(shù)y是否被賦值,如果沒有护戳,再等于默認(rèn)值翎冲。
function log(x, y) {
if (typeof y === 'undefined') {
y = 'World';
}
console.log(x, y);
}
ES6 允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值,即直接寫在參數(shù)定義的后面媳荒。function Point(x = 0, y = 0)
抗悍。 參數(shù)變量是默認(rèn)聲明的,所以不能用let或const再次聲明钳枕。
使用參數(shù)默認(rèn)值時缴渊,函數(shù)不能有同名參數(shù)。
// 不報(bào)錯
function foo(x, x, y) {
// ...
}
// 報(bào)錯
function foo(x, x, y = 1) {
// ...
}
與解構(gòu)賦值默認(rèn)值結(jié)合使用
function foo({x, y = 5}) {
console.log(x, y);
}
只有當(dāng)函數(shù)foo的參數(shù)是一個對象時鱼炒,變量x和y才會通過解構(gòu)賦值生成衔沼。如果函數(shù)foo調(diào)用時沒提供參數(shù),變量x和y就不會生成昔瞧,從而報(bào)錯指蚁。通過提供函數(shù)參數(shù)的默認(rèn)值,就可以避免這種情況自晰。
參數(shù)默認(rèn)值的位置
通常情況下凝化,定義了默認(rèn)值的參數(shù),應(yīng)該是函數(shù)的尾參數(shù)酬荞。因?yàn)檫@樣比較容易看出來搓劫,到底省略了哪些參數(shù)。如果非尾部的參數(shù)設(shè)置默認(rèn)值混巧,實(shí)際上這個參數(shù)是沒法省略的枪向,除非顯式輸入undefined。如果傳入undefined牲剃,將觸發(fā)該參數(shù)等于默認(rèn)值遣疯,null則沒有這個效果。
函數(shù)的 length 屬性
指定了默認(rèn)值以后,函數(shù)的length屬性缠犀,將返回沒有指定默認(rèn)值的參數(shù)個數(shù)数苫。也就是說,指定了默認(rèn)值后辨液,length屬性將失真虐急。
這是因?yàn)閘ength屬性的含義是,該函數(shù)預(yù)期傳入的參數(shù)個數(shù)滔迈。某個參數(shù)指定默認(rèn)值以后止吁,預(yù)期傳入的參數(shù)個數(shù)就不包括這個參數(shù)了。同理燎悍,后文的 rest 參數(shù)也不會計(jì)入length屬性敬惦。如果設(shè)置了默認(rèn)值的參數(shù)不是尾參數(shù),那么length屬性也不再計(jì)入后面的參數(shù)了谈山。
作用域
一旦設(shè)置了參數(shù)的默認(rèn)值俄删,函數(shù)進(jìn)行聲明初始化時,參數(shù)會形成一個單獨(dú)的作用域(context)奏路。等到初始化結(jié)束畴椰,這個作用域就會消失。這種語法行為鸽粉,在不設(shè)置參數(shù)默認(rèn)值時斜脂,是不會出現(xiàn)的。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
上面代碼中触机,參數(shù)y的默認(rèn)值等于變量x帚戳。調(diào)用函數(shù)f時,參數(shù)形成一個單獨(dú)的作用域威兜。在這個作用域里面销斟,默認(rèn)值變量x指向第一個參數(shù)x,而不是全局變量x椒舵,所以輸出是2蚂踊。
例子2,
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
如果此時笔宿,全局變量x不存在犁钟,就會報(bào)錯。
應(yīng)用
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
利用參數(shù)默認(rèn)值泼橘,可以指定某一個參數(shù)不得省略涝动,如果省略就拋出一個錯誤。注意函數(shù)名throwIfMissing之后有一對圓括號炬灭,這表明參數(shù)的默認(rèn)值不是在定義時執(zhí)行醋粟,而是在運(yùn)行時執(zhí)行。如果參數(shù)已經(jīng)賦值,默認(rèn)值中的函數(shù)就不會運(yùn)行米愿。
另外一個應(yīng)用厦凤,可以將參數(shù)默認(rèn)值設(shè)為undefined,表明這個參數(shù)是可以省略的育苟。
6.2 rest 參數(shù)
ES6 引入 rest 參數(shù)(形式為...變量名)较鼓,用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對象了违柏。rest 參數(shù)搭配的變量是一個數(shù)組博烂,該變量將多余的參數(shù)放入數(shù)組中。
arguments對象不是數(shù)組漱竖,而是一個類似數(shù)組的對象禽篱。所以為了使用數(shù)組的方法,必須使用Array.prototype.slice.call先將其轉(zhuǎn)為數(shù)組闲孤。rest 參數(shù)就不存在這個問題谆级,它就是一個真正的數(shù)組,數(shù)組特有的方法都可以使用讼积。
注意,rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個參數(shù))脚仔,否則會報(bào)錯勤众。函數(shù)的length屬性,不包括 rest 參數(shù)鲤脏。
6.3 嚴(yán)格模式
從 ES5 開始们颜,函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式。
ES2016 做了一點(diǎn)修改猎醇,規(guī)定只要函數(shù)參數(shù)使用了默認(rèn)值窥突、解構(gòu)賦值、或者擴(kuò)展運(yùn)算符硫嘶,那么函數(shù)內(nèi)部就不能顯式設(shè)定為嚴(yán)格模式阻问,否則會報(bào)錯。
這樣規(guī)定的原因是沦疾,函數(shù)內(nèi)部的嚴(yán)格模式称近,同時適用于函數(shù)體和函數(shù)參數(shù)。但是哮塞,函數(shù)執(zhí)行的時候刨秆,先執(zhí)行函數(shù)參數(shù),然后再執(zhí)行函數(shù)體忆畅。這樣就有一個不合理的地方衡未,只有從函數(shù)體之中,才能知道參數(shù)是否應(yīng)該以嚴(yán)格模式執(zhí)行,但是參數(shù)卻應(yīng)該先于函數(shù)體執(zhí)行缓醋。
兩種方法可以規(guī)避這種限制如失。第一種是設(shè)定全局性的嚴(yán)格模式,這是合法的改衩。第二種是把函數(shù)包在一個無參數(shù)的立即執(zhí)行函數(shù)里面岖常。
6.4 name 屬性
函數(shù)的name屬性,返回該函數(shù)的函數(shù)名葫督。這個屬性早就被瀏覽器廣泛支持竭鞍,但是直到 ES6,才將其寫入了標(biāo)準(zhǔn)橄镜。
需要注意的是偎快,ES6 對這個屬性的行為做出了一些修改。傳入匿名函數(shù)的時候:
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
如果將一個具名函數(shù)賦值給一個變量洽胶,則 ES5 和 ES6 的name屬性都返回這個具名函數(shù)原本的名字晒夹。
Function構(gòu)造函數(shù)返回的函數(shù)實(shí)例,name屬性的值為anonymous姊氓。bind返回的函數(shù)丐怯,name屬性值會加上bound前綴。
6.5 箭頭函數(shù)
如果箭頭函數(shù)的代碼塊部分多于一條語句翔横,就要使用大括號將它們括起來读跷,并且使用return語句返回。var sum = (num1, num2) => { return num1 + num2; }
由于大括號被解釋為代碼塊禾唁,所以如果箭頭函數(shù)直接返回一個對象效览,必須在對象外面加上括號(),否則會報(bào)錯荡短。
如果箭頭函數(shù)只有一行語句丐枉,且不需要返回值,可以采用下面的寫法掘托,就不用寫大括號了瘦锹。let fn = () => void doesNotReturn();
箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用。
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
箭頭函數(shù)有幾個使用注意點(diǎn)烫映。
(1)函數(shù)體內(nèi)的this對象沼本,就是定義時所在的對象,而不是使用時所在的對象锭沟。
(2)不可以當(dāng)作構(gòu)造函數(shù)抽兆,也就是說,不可以使用new命令族淮,否則會拋出一個錯誤辫红。
(3)不可以使用arguments對象凭涂,該對象在函數(shù)體內(nèi)不存在。如果要用贴妻,可以用 rest 參數(shù)代替切油。同理super、new.target不能用名惩。
(4)不可以使用yield命令澎胡,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
this指向的固定化娩鹉,并不是因?yàn)榧^函數(shù)內(nèi)部有綁定this的機(jī)制攻谁,實(shí)際原因是箭頭函數(shù)根本沒有自己的this,導(dǎo)致內(nèi)部的this就是外層代碼塊的this弯予。正是因?yàn)樗鼪]有this戚宦,所以也就不能用作構(gòu)造函數(shù)。由于箭頭函數(shù)沒有自己的this锈嫩,所以當(dāng)然也就不能用call()受楼、apply()、bind()這些方法去改變this的指向呼寸。
(this部分這里參見教程的代碼例子艳汽,很豐富。)
6.6 綁定 this
ES7提出了“函數(shù)綁定”(function bind)運(yùn)算符对雪,用來取代call 骚灸、apply、bind
調(diào)用慌植。雖然該語法還是ES7的一個提案,但是Babel轉(zhuǎn)碼器已經(jīng)支持义郑。
函數(shù)綁定運(yùn)算符是并排的兩個冒號(::)蝶柿,雙冒號左邊是一個對象,右邊是一個函數(shù)非驮。該運(yùn)算符會自動將左邊的對象交汤,作為上下文環(huán)境(即this對象),綁定到右邊的函數(shù)上面劫笙。
6.7 尾調(diào)用優(yōu)化
尾調(diào)用(Tail Call) 是函數(shù)式編程的一個重要概念芙扎,本身非常簡單,一句話就能說清楚填大,就是指某個函數(shù)的最后一步是調(diào)用另一個函數(shù)戒洼。
function f(x){
return g(x);
}
我們知道,函數(shù)調(diào)用會在內(nèi)存形成一個“調(diào)用記錄”允华,又稱“調(diào)用幀”(call frame)圈浇,保存調(diào)用位置和內(nèi)部變量等信息寥掐。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B,那么在A的調(diào)用幀上方磷蜀,還會形成一個B的調(diào)用幀召耘。等到B運(yùn)行結(jié)束,將結(jié)果返回到A褐隆,B的調(diào)用幀才會消失污它。所有的調(diào)用幀,就形成一個“調(diào)用検”(call stack)衫贬。
尾調(diào)用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調(diào)用幀虫埂,因?yàn)檎{(diào)用位置祥山、內(nèi)部變量等信息都不會再用到了,只要直接用內(nèi)層函數(shù)的調(diào)用幀掉伏,取代外層函數(shù)的調(diào)用幀就可以了缝呕。這就叫做“尾調(diào)用優(yōu)化”(Tail call optimization)。注意斧散,只有不再用到外層函數(shù)的內(nèi)部變量供常,內(nèi)層函數(shù)的調(diào)用幀才會取代外層函數(shù)的調(diào)用幀,否則就無法進(jìn)行“尾調(diào)用優(yōu)化”鸡捐。
函數(shù)調(diào)用自身栈暇,稱為遞歸。如果尾調(diào)用自身箍镜,就稱為尾遞歸源祈。遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r保存成千上百個調(diào)用幀色迂,很容易發(fā)生“棧溢出”錯誤(stack overflow)香缺。但對于尾遞歸來說,由于只存在一個調(diào)用幀歇僧,所以永遠(yuǎn)不會發(fā)生“棧溢出”錯誤图张。由此可見,“尾調(diào)用優(yōu)化”對遞歸操作意義重大诈悍,所以一些函數(shù)式編程語言將其寫入了語言規(guī)格祸轮。ES6 是如此,第一次明確規(guī)定侥钳,所有 ECMAScript 的實(shí)現(xiàn)适袜,都必須部署“尾調(diào)用優(yōu)化”。
遞歸函數(shù)的改寫慕趴。做到這一點(diǎn)的方法痪蝇,就是把所有用到的內(nèi)部變量改寫成函數(shù)的參數(shù)鄙陡。這樣做的缺點(diǎn)就是不太直觀,第一眼很難看出來躏啰。兩個方法可以解決這個問題趁矾。方法一是在尾遞歸函數(shù)之外,再提供一個正常形式的函數(shù)给僵。第二種方法就簡單多了毫捣,就是采用 ES6 的函數(shù)默認(rèn)值。
總結(jié)一下帝际,遞歸本質(zhì)上是一種循環(huán)操作蔓同。純粹的函數(shù)式編程語言沒有循環(huán)操作命令,所有的循環(huán)都用遞歸實(shí)現(xiàn)蹲诀,這就是為什么尾遞歸對這些語言極其重要斑粱。
ES6 的尾調(diào)用優(yōu)化只在嚴(yán)格模式下開啟,正常模式是無效的脯爪。
關(guān)于尾遞歸優(yōu)化的實(shí)現(xiàn)看教程代碼则北,有點(diǎn)小復(fù)雜。
6.8 函數(shù)參數(shù)的尾逗號
ES2017 允許函數(shù)的最后一個參數(shù)有尾逗號(trailing comma)痕慢。
此前尚揣,函數(shù)定義和調(diào)用時,都不允許最后一個參數(shù)后面出現(xiàn)逗號掖举。
這樣的規(guī)定也使得快骗,函數(shù)參數(shù)與數(shù)組和對象的尾逗號規(guī)則,保持一致了塔次。但是 JSON 不支持尾后逗號方篮。
6.9 catch 語句的參數(shù)
目前,有一個提案励负,允許try...catch
結(jié)構(gòu)中的catch
語句調(diào)用時不帶有參數(shù)恭取。這個提案跟參數(shù)有關(guān),也放在這一章介紹熄守。
七,數(shù)組的擴(kuò)展
7.1 擴(kuò)展運(yùn)算符(spread)
...
是擴(kuò)展運(yùn)算符耗跛。它好比 rest 參數(shù)的逆運(yùn)算裕照,將一個數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。Rest操作符一般用在函數(shù)參數(shù)的聲明中调塌,而Spread用在函數(shù)的調(diào)用中晋南。
由于擴(kuò)展運(yùn)算符可以展開數(shù)組,所以不再需要apply方法羔砾,將數(shù)組轉(zhuǎn)為函數(shù)的參數(shù)了负间。
擴(kuò)展運(yùn)算符的應(yīng)用
(1)復(fù)制數(shù)組
數(shù)組是復(fù)合的數(shù)據(jù)類型偶妖,直接復(fù)制的話,只是復(fù)制了指向底層數(shù)據(jù)結(jié)構(gòu)的指針政溃,而不是克隆一個全新的數(shù)組瓦糕。
ES5 只能用變通方法來復(fù)制數(shù)組纷责。
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
擴(kuò)展運(yùn)算符提供了復(fù)制數(shù)組的簡便寫法。
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;
(2)合并數(shù)組
擴(kuò)展運(yùn)算符提供了數(shù)組合并的新寫法。
// ES5的合并數(shù)組
arr1.concat(arr2, arr3);
// ES6的合并數(shù)組
[...arr1, ...arr2, ...arr3]
(3)與解構(gòu)賦值結(jié)合
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
如果將擴(kuò)展運(yùn)算符用于數(shù)組賦值挚歧,只能放在參數(shù)的最后一位,否則會報(bào)錯顷蟀。
(4)字符串
有一個重要的好處丐膝,那就是能夠正確識別四個字節(jié)的 Unicode 字符。
(5)實(shí)現(xiàn)了 Iterator 接口的對象
任何 Iterator 接口的對象(參閱 Iterator 一章)淫半,都可以用擴(kuò)展運(yùn)算符轉(zhuǎn)為真正的數(shù)組溃槐。如let array = [...nodeList];
。對于那些沒有部署 Iterator 接口的類似數(shù)組的對象科吭,擴(kuò)展運(yùn)算符就無法將其轉(zhuǎn)為真正的數(shù)組昏滴。
(6)Map 和 Set 結(jié)構(gòu),Generator 函數(shù)
7.2 Array.from()
Array.from方法用于將兩類對象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對象(array-like object)和可遍歷(iterable)的對象(包括ES6新增的數(shù)據(jù)結(jié)構(gòu)Set和Map)砌溺。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
實(shí)際應(yīng)用中影涉,常見的類似數(shù)組的對象是DOM操作返回的NodeList
集合,以及函數(shù)內(nèi)部的arguments
對象规伐。Array.from都可以將它們轉(zhuǎn)為真正的數(shù)組蟹倾。
值得提醒的是,擴(kuò)展運(yùn)算符(...)
也可以將某些數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)為數(shù)組猖闪。擴(kuò)展運(yùn)算符背后調(diào)用的是遍歷器接口(Symbol.iterator)鲜棠,如果一個對象沒有部署這個接口,就無法轉(zhuǎn)換培慌。
Array.from方法還支持類似數(shù)組的對象豁陆。所謂類似數(shù)組的對象,本質(zhì)特征只有一點(diǎn)吵护,即必須有l(wèi)ength屬性盒音。因此,任何有l(wèi)ength屬性的對象馅而,都可以通過Array.from方法轉(zhuǎn)為數(shù)組祥诽,而此時擴(kuò)展運(yùn)算符就無法轉(zhuǎn)換。
// arguments對象
function foo() {
const args = [...arguments];
}
// NodeList對象
[...document.querySelectorAll('div')]
Array.from還可以接受第二個參數(shù)瓮恭,作用類似于數(shù)組的map方法雄坪,用來對每個元素進(jìn)行處理,將處理后的值放入返回的數(shù)組屯蹦。
Array.from()的另一個應(yīng)用是维哈,將字符串轉(zhuǎn)為數(shù)組绳姨,然后返回字符串的長度。因?yàn)樗苷_處理各種Unicode字符阔挠,可以避免JavaScript將大于\uFFFF的Unicode字符飘庄,算作兩個字符的bug。
7.3 Array.of()
Array.of方法用于將一組值谒亦,轉(zhuǎn)換為數(shù)組竭宰。Array.of總是返回參數(shù)值組成的數(shù)組。如果沒有參數(shù)份招,就返回一個空數(shù)組切揭。Array.of基本上可以用來替代Array()或new Array(),并且不存在由于參數(shù)不同而導(dǎo)致的重載锁摔。它的行為非常統(tǒng)一廓旬。
這個方法的主要目的,是彌補(bǔ)數(shù)組構(gòu)造函數(shù)Array()的不足谐腰。因?yàn)閰?shù)個數(shù)的不同孕豹,會導(dǎo)致Array()的行為有差異。只有當(dāng)參數(shù)個數(shù)不少于2個時十气,Array()才會返回由參數(shù)組成的新數(shù)組励背。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
7.4 數(shù)組實(shí)例的 copyWithin()
Array.prototype.copyWithin(target, start = 0, end = this.length)
7.5 數(shù)組實(shí)例的 find() 和 findIndex()
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
數(shù)組實(shí)例的findIndex方法的用法與find方法非常類似,返回第一個符合條件的數(shù)組成員的位置砸西,如果所有成員都不符合條件叶眉,則返回-1。這兩個方法都可以接受第二個參數(shù)芹枷,用來綁定回調(diào)函數(shù)的this對象衅疙。另外,這兩個方法都可以發(fā)現(xiàn)NaN鸳慈,彌補(bǔ)了數(shù)組的IndexOf方法的不足饱溢。indexof它內(nèi)部使用嚴(yán)格相等運(yùn)算符(===)進(jìn)行判斷,這會導(dǎo)致對NaN的誤判走芋。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
7.6 數(shù)組實(shí)例的fill()
fill方法使用給定值绩郎,填充一個數(shù)組。fill方法還可以接受第二個和第三個參數(shù)翁逞,用于指定填充的起始位置和結(jié)束位置嗽上。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
7.7 數(shù)組實(shí)例的 entries(),keys() 和 values()
它們都返回一個遍歷器對象(詳見《Iterator》一章)熄攘,可以用for...of循環(huán)進(jìn)行遍歷。如果不使用for...of循環(huán)彼念,可以手動調(diào)用遍歷器對象的next方法挪圾,進(jìn)行遍歷浅萧。
7.8 數(shù)組實(shí)例的 includes()
與字符串的includes方法類似,參數(shù)負(fù)的情況稍有不同哲思。
7.9 數(shù)組的空位
數(shù)組的空位指洼畅,數(shù)組的某一個位置沒有任何值。比如棚赔,Array構(gòu)造函數(shù)返回的數(shù)組都是空位帝簇。Array(3) // [, , ,]
注意,空位不是undefined靠益,一個位置的值等于undefined丧肴,依然是有值的‰屎螅空位是沒有任何值芋浮,in運(yùn)算符可以說明這一點(diǎn)。
ES5 對空位的處理壳快,已經(jīng)很不一致了纸巷,大多數(shù)情況下會忽略空位。
forEach(), filter(), every() 和some()都會跳過空位眶痰。
map()會跳過空位瘤旨,但會保留這個值
join()和toString()會將空位視為undefined,而undefined和null會被處理成空字符串竖伯。
ES6 則是明確將空位轉(zhuǎn)為undefined存哲。
由于空位的處理規(guī)則非常不統(tǒng)一,所以建議避免出現(xiàn)空位黔夭。
八宏胯,對象的擴(kuò)展
8.1 屬性的簡潔表示法
ES6 允許直接寫入變量和函數(shù),作為對象的屬性和方法本姥。這樣的書寫更加簡潔肩袍。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
除了屬性簡寫,方法也可以簡寫婚惫。
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
如果某個方法的值是一個 Generator 函數(shù)氛赐,前面需要加上星號。
8.2 屬性名表達(dá)式
但是先舷,如果使用字面量方式定義對象(使用大括號)艰管,在 ES5 中只能使用方法一(標(biāo)識符)定義屬性。
var obj = {
foo: true,
abc: 123
};
ES6 允許字面量定義對象時蒋川,用方法二(表達(dá)式)作為對象的屬性名牲芋,即把表達(dá)式放在方括號內(nèi)。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
表達(dá)式還可以用于定義方法名。注意缸浦,屬性名表達(dá)式與簡潔表示法夕冲,不能同時使用,會報(bào)錯裂逐。
注意歹鱼,屬性名表達(dá)式如果是一個對象,默認(rèn)情況下會自動將對象轉(zhuǎn)為字符串[object Object]
卜高,這一點(diǎn)要特別小心弥姻。
8.3 方法的 name 屬性
函數(shù)的name屬性,返回函數(shù)名掺涛。對象方法也是函數(shù)庭敦,因此也有name屬性。如果對象的方法使用了取值函數(shù)(getter)和存值函數(shù)(setter)鸽照,則name屬性不是在該方法上面螺捐,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set矮燎。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
有兩種特殊情況:bind方法創(chuàng)造的函數(shù)定血,name屬性返回bound加上原函數(shù)的名字;Function構(gòu)造函數(shù)創(chuàng)造的函數(shù)诞外,name屬性返回anonymous澜沟。如果對象的方法是一個 Symbol 值,那么name屬性返回的是這個 Symbol 值的描述峡谊。
8.4 Object.is()
ES5 比較兩個值是否相等茫虽,只有兩個運(yùn)算符:相等運(yùn)算符(==)和嚴(yán)格相等運(yùn)算符(===)。它們都有缺點(diǎn)既们,前者會自動轉(zhuǎn)換數(shù)據(jù)類型濒析,后者的NaN不等于自身,以及+0等于-0啥纸。JavaScript 缺乏一種運(yùn)算号杏,在所有環(huán)境中,只要兩個值是一樣的斯棒,它們就應(yīng)該相等盾致。
ES6 提出“Same-value equality”(同值相等)算法,用來解決這個問題荣暮。Object.is就是部署這個算法的新方法庭惜。
8.5 Object.assign()
Object.assign方法用于對象的合并,將源對象(source)的所有可枚舉屬性穗酥,復(fù)制到目標(biāo)對象(target)护赊。Object.assign方法的第一個參數(shù)是目標(biāo)對象惠遏,后面的參數(shù)都是源對象。Object.assign拷貝的屬性是有限制的骏啰,只拷貝源對象的自身屬性(不拷貝繼承屬性)爽哎,也不拷貝不可枚舉的屬性(enumerable: false)。屬性名為 Symbol 值的屬性器一,也會被Object.assign拷貝。
注意厨内,如果目標(biāo)對象與源對象有同名屬性祈秕,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性雏胃。
注意點(diǎn)
(1)淺拷貝(2)同名屬性的替換(3)數(shù)組處理時視為對象 (4)取值函數(shù)的處理將求值后再復(fù)制请毛。
應(yīng)用:(1)為對象添加屬性(2)為對象添加方法(3)克隆對象(4)合并多個對象(5)為屬性指定默認(rèn)值
8.6 屬性的可枚舉性和遍歷
可枚舉性
對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行為瞭亮。ES5中 Object.getOwnPropertyDescriptor 方法可以獲取該屬性的描述對象方仿。描述對象的enumerable屬性,稱為”可枚舉性“统翩,如果該屬性為false仙蚜,就表示某些操作會忽略當(dāng)前屬性。
目前厂汗,有四個操作會忽略enumerable為false的屬性委粉。
for...in循環(huán):只遍歷對象自身的和繼承的可枚舉的屬性。
Object.keys():返回對象自身的所有可枚舉的屬性的鍵名娶桦。
JSON.stringify():只串行化對象自身的可枚舉的屬性贾节。
Object.assign(): 忽略enumerable為false的屬性,只拷貝對象自身的可枚舉的屬性衷畦。
另外栗涂,ES6 規(guī)定,所有 Class 的原型的方法都是不可枚舉的祈争〗锍蹋總的來說,操作中引入繼承的屬性會讓問題復(fù)雜化铛嘱,大多數(shù)時候暖释,我們只關(guān)心對象自身的屬性。所以墨吓,盡量不要用for...in循環(huán)球匕,而用Object.keys()代替。
屬性的遍歷
ES6 一共有5種方法可以遍歷對象的屬性帖烘。
(1)for...in
(2)Object.keys(obj
(3)Object.getOwnPropertyNames(obj)(4)Object.getOwnPropertySymbols(obj)
(5)Reflect.ownKeys(obj)
以上的5種方法遍歷對象的鍵名亮曹,都遵守同樣的屬性遍歷的次序規(guī)則。
首先遍歷所有數(shù)值鍵,按照數(shù)值升序排列照卦。
其次遍歷所有字符串鍵式矫,按照加入時間升序排列。
最后遍歷所有 Symbol 鍵役耕,按照加入時間升序排列采转。
8.7 Object.getOwnPropertyDescriptors()
ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定對象所有自身屬性(非繼承屬性)的描述對象瞬痘。
該方法的引入目的故慈,主要是為了解決Object.assign()無法正確拷貝get屬性和set屬性的問題。這是因?yàn)镺bject.assign方法總是拷貝一個屬性的值框全,而不會拷貝它背后的賦值方法或取值方法察绷。這時,Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法津辩,就可以實(shí)現(xiàn)正確拷貝拆撼。
Object.getOwnPropertyDescriptors方法的另一個用處,是配合Object.create方法喘沿,將對象屬性克隆到一個新對象闸度。這屬于淺拷貝。
另外摹恨,Object.getOwnPropertyDescriptors方法可以實(shí)現(xiàn)一個對象繼承另一個對象筋岛。
8.8 _proto_屬性, Object.setPrototypeOf()晒哄, Object.getPrototypeOf()
JavaScript 語言的對象繼承是通過原型鏈實(shí)現(xiàn)的睁宰。ES6 提供了更多原型對象的操作方法。
proto屬性 該屬性沒有寫入 ES6 的正文寝凌,而是寫入了附錄柒傻,原因是proto前后的雙下劃線,說明它本質(zhì)上是一個內(nèi)部屬性较木,而不是一個正式的對外的 API红符,只是由于瀏覽器廣泛支持,才被加入了 ES6伐债。標(biāo)準(zhǔn)明確規(guī)定预侯,只有瀏覽器必須部署這個屬性,其他運(yùn)行環(huán)境不一定需要部署峰锁,而且新的代碼最好認(rèn)為這個屬性是不存在的萎馅。使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)虹蒋、Object.create()(生成操作)代替糜芳。
Object.setPrototypeOf() Object.setPrototypeOf方法的作用與proto相同飒货,用來設(shè)置一個對象的prototype對象,返回參數(shù)對象本身峭竣。它是 ES6 正式推薦的設(shè)置原型對象的方法塘辅。
Object.getPrototypeOf() 該方法與Object.setPrototypeOf方法配套,用于讀取一個對象的原型對象皆撩。
8.9 super 關(guān)鍵字
我們知道扣墩,this關(guān)鍵字總是指向函數(shù)所在的當(dāng)前對象,ES6 又新增了另一個類似的關(guān)鍵字super扛吞,指向當(dāng)前對象的原型對象沮榜。注意,super關(guān)鍵字表示原型對象時喻粹,只能用在對象的方法之中,用在其他地方都會報(bào)錯草巡。目前守呜,只有對象方法的簡寫法可以讓 JavaScript 引擎確認(rèn),定義的是對象的方法山憨。
const obj = {
find() {
return super.foo;
}
};
JavaScript 引擎內(nèi)部查乒,super.foo等同于Object.getPrototypeOf(this).foo(屬性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
8.10 Object.keys()郁竟,Object.values()玛迄,Object.entries()
ES5 引入了Object.keys方法,返回一個數(shù)組棚亩,成員是參數(shù)對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名蓖议。
ES2017 引入了跟Object.keys 配套的Object.values 和Object.entries ,作為遍歷一個對象的補(bǔ)充手段讥蟆,供for...of 循環(huán)使用勒虾。
Object.entries的基本用途是遍歷對象的屬性。Object.entries方法的另一個用處是瘸彤,將對象轉(zhuǎn)為真正的Map結(jié)構(gòu)修然。
8.11 對象的擴(kuò)展運(yùn)算符(spread)
ES2017 將這個運(yùn)算符引入了對象。
(1)解構(gòu)賦值
單純的解構(gòu)賦值质况,所以可以讀取對象繼承的屬性愕宋;擴(kuò)展運(yùn)算符的解構(gòu)賦值,只能讀取對象o自身的屬性结榄。
(2)擴(kuò)展運(yùn)算符
擴(kuò)展運(yùn)算符(...)用于取出參數(shù)對象的所有可遍歷屬性中贝,拷貝到當(dāng)前對象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
這等同于使用Object.assign方法潭陪。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
擴(kuò)展運(yùn)算符的參數(shù)對象之中雄妥,如果有取值函數(shù)get最蕾,這個函數(shù)是會執(zhí)行的。
8.12 Null 傳導(dǎo)運(yùn)算符
編程實(shí)務(wù)中老厌,如果讀取對象內(nèi)部的某個屬性瘟则,往往需要判斷一下該對象是否存在。比如枝秤,要讀取message.body.user.firstName
醋拧,安全的寫法是寫成下面這樣。const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';
這樣的層層判斷非常麻煩淀弹,因此現(xiàn)在有一個提案丹壕,引入了“Null 傳導(dǎo)運(yùn)算符”(null propagation operator)?.
,簡化上面的寫法薇溃。const firstName = message?.body?.user?.firstName || 'default';
上面代碼有三個?.運(yùn)算符菌赖,只要其中一個返回null或undefined,就不再往下運(yùn)算沐序,而是返回undefined琉用。