2021年,快速了解 ES2022 新特性(二)
點擊上方?前端瓶子君,關(guān)注公眾號
回復算法,加入前端編程面試算法每日一題群

接著上一篇快速了解ES特性之一(ES7-ES8)[3] ,今天我們主要來講ES新特性之 ES9 。上篇中我們已經(jīng)講了ES的由來,版本對應規(guī)則等等內(nèi)容,這里我就不再贅述。讓我們直接開始吧
快速了解ES特性 是我的系列文章,現(xiàn)在已有
快速了解ES特性之一(ES7-ES8)[4] 快速了解ES特性之二(ES9)[5]
ES2018 / ES9
Lifting template literal restriction 提升模板文字限制
字符串模板語法是 ES6 的特性(很不巧的是,我根據(jù) 已完成的ES提案[6] 這里來講的,所以沒有寫 ES6 相關(guān)的內(nèi)容,后面會寫一個來補充)。在 ES6 的版本中,我們不能在帶標簽的模板字符串中插入像 \unicode 之類的 錯誤 轉(zhuǎn)義字符,如果使用這類錯誤的轉(zhuǎn)義字符導致 bad escape sequence: \unicode 的錯誤。在 ES9 中則放開了這些限制,讓這些錯誤的轉(zhuǎn)義也可以正確的執(zhí)行。下面我們舉例說明一下
function?hi(strings)?{
??console.log(strings);
}
let?words?=?hi`hi,?\ustar;`;
復制代碼
這里的 \ustar 很明顯不是一個正確的 Unicode ,我們這里使用 es-check[7] 指定 es6 來檢驗一下是否正確
es-check?es6?test.js
//?SyntaxError:?Bad?character?escape?sequence?(4:21)
復制代碼
很明顯和我們上文中說的錯誤一樣,無法正確識別轉(zhuǎn)義字符。我們這次將 es-check[8] 版本指定為 es9 ,再次檢測,no errors!
es-check?es9?test.js
//?ES-Check:?there?were?no?ES?version?matching?errors!
復制代碼
類似的像 \x 打頭的非十六進制、 \123 這種錯誤的轉(zhuǎn)義,現(xiàn)在在 ES9 中都是 ok 的。
s (dotAll) flag for regular expressions
在正則表達式模式中,我們可以使用點 . 匹配任意的單個字符。但是這里面卻有兩個例外,默認:
.不匹配 星體字符.不匹配 行終止符
默認不匹配 星體字符,我們可以通過給正則設置 u (unicode) 這個標志來解決這個問題。但是對于 行終止符 來說,卻沒有一個類似的 flag 來解決這個問題。按照正常的正則語義,點 . 可以匹配任意字符,但實際上只能識別下面列出的 行終止符 :
U+000A 換行 (LF) ( \n)U+000D 回車 (CR) ( \r)U+2028 線分隔符 U+2029 段落分隔符
在實際的場景中,還有一些字符可以被認為是 行終止符 ,比如說:
U+000B 垂直制表符 ( \v)U+000C 換頁 ( \f)U+0085 下一行
像 java 中,我們可以指定 flag Pattern.DOTALL 來讓 . 匹配所有;在 C# 則用 RegexOptions.Singleline 來匹配所有。所以 es9 新增了一個 flag s,來補充上面的場景。下面我們舉例來說明一下
const?str?=?`
hello
world
`;
const?r1?=?/hello.world/;
console.log(r1.test(str),?r1.dotAll,?r1.flags);
//?false?false
const?r2?=?/hello.world/s;?//?添加?'s'?flag
console.log(r2.test(str),?r2.dotAll,?r2.flags);
//?true?true?s
復制代碼
有的同學可能會有這樣的疑問,這里為啥叫 s ,s 代表的 singleline 不會和 m ( multiline ) 沖突嗎 ?用官方的話來說:s 代表就是 singleline,也是 dotAll,含義一致。我們不好意思加一個新的標記來做這件事情。s ( dotAll )表示的是讓 . 可以匹配任意字符,和 m ( multiline ) 標記不沖突。大家愛咋用就咋用 ??
RegExp named capture groups 正則命名捕獲組
這個特性就比較花哨了,在以往的代碼中,假如我們要匹配一個日期中的年月日,我們可能是這樣做的
const?str?=?"2021-10-24";
const?r1?=?/(\d{4})-(\d{2})-(\d{2})/;
const?groups?=?r1.exec(str);
console.log("year:",?groups[1],?"month:",?groups[2],?"day:",?groups[3]);
//?year:?2021?month:?10?day:?24
復制代碼
沒毛病啊,老鐵 ??,我所知道的幾門語言都是這樣做的。在這個語法出來之前,我都沒有思考過直接用上面的語法寫有啥毛病。按照定式思維,大家都這樣做,那這樣做就是對的。但是這個用官方的話來說:我們?nèi)绻闯R?guī)的寫法來寫正則,我們在使用這個 groups 的時候,假如我要獲取 月份 這個值,那么就需要仔細看一下這個正則表達式,我們用 括號 包的月份這個值,數(shù)一下,在第二個,按照正則的規(guī)則,groups 的第 0 個是表達式匹配的整串,要獲取后面分組出來的值需要 +1 ,所以我們要獲取 月份 這個值的索引應該是 2 ,最終結(jié)果:groups[2] 。是不是很麻煩,還很容易錯?我:黑人問號.jpg。要這么說也沒毛病,現(xiàn)在我們來看看這個規(guī)范是怎么樣的:在先前分組的內(nèi)容前面加上一個 ?<給分組取的別名>,整的在一起就是 (?<給分組取的別名>...) 。我們把上面的示例修改一下
const?str?=?"2021-10-24";
const?r1?=?/(?\d{4})-(?\d{2})-(?\d{2})/ ;
const?exec?=?r1.exec(str);
console.log("year:",?exec[1],?"month:",?exec[2],?"day:",?exec[3]);
console.log("year:",?exec.groups.year,?"month:",?exec.groups.month,?"day:",?exec.groups.day);
//?const?{?groups:?{?year,?month,?day?}?}?=?exec;
//?console.log("year:",?year,?"month:",?month,?"day:",?day);
//?year:?2021?month:?10?day:?24
//?year:?2021?month:?10?day:?24
復制代碼
是不是花哨起來了?no,no,no。這并不是全部,我們還可以進行更花哨的匹配。如果我們需要匹配一個復雜重復字符串,比如我們需要匹配一個文本是否包含 aaabbbccc任何字符aaabbbccc任何字符aaabbbccc ,常規(guī)的寫法如下
const?str?=?'asjdhkjlhsdkjaaabbbcccahsdkjashdjhsaaaabbbcccasjhdkljdhjkdaaaabbbcccashdlkj';
const?r1?=?/a{3,}b{3}c{3,}.+a{3,}b{3}c{3,}.+a{3,}b{3}c{3,}/;
const?result?=?r1.test(str);
console.log(result);
//?true
復制代碼
是不是感覺上面的正則有些繁瑣呢?這里我們可以使用這個命名捕獲進行更加花哨的使用,這也是命名捕獲的一個新特性,如果我們需要匹配一個和前某個表達式一樣的結(jié)果,我們可以在表達式中用 \k
const?str?=?'asjdhkjlhsdkjaaabbbcccahsdkjashdjhsaaaabbbcccasjhdkljdhjkdaaaabbbcccashdlkj';
const?r1?=?/(?a{3,}b{3}c{3,}).+\k.+\k/ ;
const?result?=?r1.test(str);
console.log(result);
//?true
復制代碼
現(xiàn)在看起來是不是要好很多呢?上面的例子比較簡單,如果遇上更加復雜的情況,這個特性可以幫我們少寫很多重復的規(guī)則,并且更加的準確,減少出錯的概率。
這個命名捕獲同樣適用于字符串替換,我們還是用上面的日期做示例吧。假如我們需要把一個日期從 yyyy-MM-dd 變成 dd/MM/yyyy ,傳統(tǒng)的寫法是
const?str?=?"2021-10-24";
const?r1?=?/(\d{4})-(\d{2})-(\d{2})/;
const?rd?=?str.replace(r1,"$3/$2/$1");
console.log(rd);
//?24/10/2021
復制代碼
我們修改成命名捕獲
const?str?=?"2021-10-24";
const?r1?=?/(?\d{4})-(?\d{2})-(?\d{2})/ ;
const?rd?=?str.replace(r1,"$/$/$" );
console.log(rd);
//?24/10/2021
復制代碼
確實更加快速確準,且不易出錯
Rest/Spread Properties
在 ES6 中我們已經(jīng)有了對數(shù)組的解構(gòu)賦值的剩余元素和數(shù)組字符串的展開方法。在 ES9 中則增加了對象的解構(gòu)賦值剩余屬性和對象字面量的展開。這個沒有啥多說的,直接舉例吧
const?obj?=?{
??x:?1,?y:?2,?z:?3,?a:?"x",?b:?"y",?c:?"z",
};
const?{?x,?y,?z,?...letter?}?=?obj;
console.log(x,?y,?z,?letter);
//?1?2?3?{?a:?'x',?b:?'y',?c:?'z'?}
復制代碼
這個 Rest Properties ,我不知道用那個詞來準確描述它,但是用起來卻是很順手。就如上面例子中的,我們從對象中解出 x, y, z ,并用 ... 這個展開符,將剩下的 a, b, c 重新組合到了新的對象 letter 中,原始的 obj 被吃光抹盡,所有都被提取出來了。所以 Rest Properties 就是讓 x, y, z 出來接客,讓剩下的 a, b, c 到 letter 中休息?????
我們接著講展開,使用上面的例子
//?...
const?objClone?=?{
??x,?y,?z,?...letter,
};
console.log(objClone);
//?{?x:?1,?y:?2,?z:?3,?a:?'x',?b:?'y',?c:?'z'?}
復制代碼
這里展開符就和其義一毛一樣了,我們把 letter 里面的 a, b, c ,展開成一個個的屬性復制給 objClone 。這里就沒有什么理解偏差,展開符嘛,不就是把這些元素啊,屬性啊,拿出來給新的對象嗎?
RegExp Lookbehind Assertions 正則表達式回溯斷言
ES9 之前 EMACScript 正則只支持先行斷言,到了 ES9 正式支持后行斷言。這里我不多做展開講,因為這個不涉及特殊的語法,后面我會單獨寫一篇文章來講解 正則表達式 ,各語言基本通用,一把梭哈。
下面我就簡簡單單舉個例子:假如我們需要匹配一個字符串 xyz 只有當它前面是 uvw 時才匹配
在沒有后行斷言的時候,如果要匹配,我們一般要這樣寫
const?str?=?"rstuvwxyz123";
const?r?=?/uvw(xyz)/;
const?result?=?r.exec(str);
console.log(result);
//?[?'uvwxyz',?'xyz',?index:?3,?input:?'rstuvwxyz123',?groups:?undefined?]
復制代碼
有了后行斷言之后
const?str?=?"rstuvwxyz123";
const?r?=?/(?<=uvw)xyz/;
const?result?=?r.exec(str);
console.log(result);
//?[?'xyz',?index:?6,?input:?'rstuvwxyz123',?groups:?undefined?]
復制代碼
有的同學肯定就疑惑了?上面那種不是更簡單嗎 ?? 。我覺得有道理,所以我把結(jié)果打印了一手。如果我們僅僅判斷一下是否匹配,確實用第一種方式,如果我們需要匹配結(jié)果,這兩種寫法就有了區(qū)別。如果使用后行斷言,uvw 是不會出現(xiàn)在匹配結(jié)果中的。
上面我們講的僅僅只是后行斷言的 正面斷言 ,還有與之相反的 負面斷言(反向否定查找) 。我們接著使用上面的例子,但是這次我們需要的是 xyz 前面不是 uvw 時才匹配
const?str?=?"rstuvwxyz123";
const?r?=?/(?;
const?result?=?r.exec(str);
console.log(result);
//?null
復制代碼
現(xiàn)在 xyz 前面緊挨著 uvw ,所以啥都匹配不到,我們修改一下,在 uvw 中間插入一個 0
const?str?=?"rstuv0wxyz123";
const?r?=?/(?;
const?result?=?r.exec(str);
console.log(result);
//?[?'xyz',?index:?7,?input:?'rstuv0wxyz123',?groups:?undefined?]
復制代碼
現(xiàn)在就我們可以匹配到了
RegExp Unicode Property Escapes 正則表達式中的 Unicode 屬性轉(zhuǎn)義
我們在使用正則表達式的時候,常常需要匹配出不同的語言文字特殊符號等。比如說我們常見的需求:某個表單中不能夠輸入 emoji 或者其他的特殊字符,一丟丟的其他語種啊之類的,按照先前的寫法,我們常常得引入三方庫來匹配這些 奇怪的字符 ,但是這些 奇怪的字符 的范圍一直在不停的變化,所以我們引入的這些庫就存在需要經(jīng)常更新的問題。比如 emoji 就是更新狂魔,要匹配 emoji 就不得不不斷更新 emoji 匹配庫才能正確的過濾掉它們。現(xiàn)在我們只需要這樣
const?str?=?"hi,???";
const?r1?=?/\p{Emoji}/gu;
console.log(r1.exec(str));
//?['??',?index:?4,?input:?'hi,???',?groups:?undefined]
復制代碼
就可以輕易的匹配到 emoji 表情,???有的同學可能直接就黑人問號.jpg了,對的,你沒有看錯,只需要這樣你就可以匹配到 emoji 了,這里也涉及了一丟丟 Unicode Emoji 的東西,想了解詳情的同學,可以在 這兒[9] 查看到詳情和更多類似花括號包裹的 Emoji 這樣的東西,比如更常用的 Emoji_Presentation ?
如果我想要匹配不是 Emoji 的呢?我們只需要把小 p 換成大 P 即可,是不是很簡單呢?
const?str?=?"hi,???";
const?r2?=?/(\P{Emoji})*/gu;
console.log(r2.exec(str));
//?['hi,?',?'?',?index:?0,?input:?'hi,???',?groups:?undefined]
復制代碼
這個特性很強,但是建議先不急著用,em em em,畢竟也得看瀏覽器嘛。當然這個特性也不止這點東西,想要了解更多這個特性的同學,可以到這兒 MDN \- Unicode property escapes[10] 仔細研讀,我也不多講了,大家加油
Promise.prototype.finally
這個就真的沒啥講頭了咯,這個方法是在 Promise then 或者 catch 執(zhí)行完之后執(zhí)行。一般就用來修改加載狀態(tài)或者某種用完就需要關(guān)閉的資源,比如:
this.loading?=?true;
xxxApi
??.listUser()
??.then((resp)?=>?{
????//?do?something...
??})
??.catch((e)?=>?{
????//?do?something...
??})
??.finally(()?=>?{
????this.loading?=?false;
??});
復制代碼
不單單只是上面說的兩種場景,如果我們需要在任務執(zhí)行完做某件事的時候,都可以用這個方法實現(xiàn)。
Asynchronous Iteration 異步迭代器
在 ES9 中,新增了 for-await-of 的用法。對于同步的迭代器,假如我們有一個 Promise 數(shù)組,要等著 Promise 元素一個個執(zhí)行完,如果按我們之前的方式
function?newPromise(delay)?{
??return?new?Promise((resolve)?=>?{
????setTimeout(()?=>?{
??????console.log(`resolve:`,?delay);
??????resolve(delay);
????},?delay);
??});
}
async?function?test()?{
??const?arr?=?[newPromise(3000),?newPromise(2000),?newPromise(4000)];
??const?before?=?Date.now();
??for?(const?item?of?arr)?{
????console.log(Date.now(),?await?newPromise(1234));
????console.log(Date.now(),?await?item);
??}
??console.log(Date.now()?-?before);
}
test();
//?resolve:?1234
//?1635088618117?1234
//?resolve:?2000
//?resolve:?3000
//?1635088619374?3000
//?resolve:?4000
//?resolve:?1234
//?1635088621117?1234
//?1635088622369?2000
//?resolve:?1234
//?1635088622369?1234
//?1635088623616?4000
//?5500
復制代碼
如果我們換成 for-await-of
async?function?test2()?{
??const?arr?=?[newPromise(3000),?newPromise(2000),?newPromise(4000)];
??const?before?=?Date.now();
??for?await?(const?item?of?arr)?{
????console.log(Date.now(),?await?newPromise(1234));
????console.log(Date.now(),?item);
??}
??console.log(Date.now()?-?before);
}
test2();
//?resolve:?2000
//?resolve:?3000
//?resolve:?4000
//?resolve:?1234
//?1635088545345?1234
//?1635088546584?3000
//?resolve:?1234
//?1635088546584?1234
//?1635088547826?2000
//?resolve:?1234
//?1635088547826?1234
//?1635088549066?4000
//?6733
復制代碼
如果就單單看結(jié)果而言,基本沒差,最終結(jié)果都一樣。但實際的執(zhí)行方式還是有差別:
直接在循環(huán)中 await 的方式,是每一個獨立的 Promise 的等待,在第一個的打印中我們可以看出來,所有的任務根據(jù) delay 的時長,依次 resolve 完 使用 for-await-of 會將數(shù)組的統(tǒng)一處理,然后再執(zhí)行循環(huán)體內(nèi)的代碼。
說到底還是往事件循環(huán)里推事件的時機不同,實際業(yè)務場景中的使用,還得看各位同學的需求,不同的寫法還是有差別的,別弄錯了就成。
說完同步,現(xiàn)在我們來說說 異步迭代器 ,在 ES9 中新增了一個 Symbol.asyncIterator[11]符號用來給對象自定義默認的異步迭代器。
下面我們舉個例子來創(chuàng)建一個異步可迭代對象
const?ai?=?{
??//?整一個方法用來結(jié)束
??dispose()?{
????this.disposed?=?true;
??},
??[Symbol.asyncIterator]()?{
????return?{
??????//?下面的方法都用上箭頭函數(shù),免得手寫that
??????next:?()?=>?{
????????return?new?Promise((resolve,?_)?=>?{
??????????setTimeout(()?=>?{
????????????resolve({
??????????????done:?!!this.disposed,
??????????????value:?Date.now(),
????????????});
??????????},?200);
????????});
??????},
????};
??},
};
async?function?test()?{
??for?await?(const?it?of?ai)?{
????console.log(it);
??}
}
test();
setTimeout(()?=>?{
??ai.dispose();
},?1000);?//?一秒后結(jié)束
//?1635170851461
//?1635170851679
//?1635170851881
//?1635170852084
復制代碼
上面的例子中,我們在一個對象中,聲明了一個 Symbol.asyncIterator 的屬性方法,這個屬性方法返回一個對象,對象中包含一個 next 方法。看起來是不是很熟悉?我們聲明一個同步迭代器,也是這樣做的。再看看這個方法的返回值,如果使用同步迭代器,我們直接返回 { value , done } 即可,value 表示實際的值,done 是個布爾值,用來標記迭代器是否迭代完成。在異步迭代器中,我們返回的是一個 Promise 對象,返回的內(nèi)容則是 Promise resolve({ value , done }) 。value 和 done 的含義和上述同步迭代器一致。
有的同學可能想,這么寫是不是太麻煩了,我們在同步迭代器中可以這樣寫
const?iterator?=?{
??[Symbol.iterator]:?function*?()?{
????yield?`a`;
????yield?`b`;
????yield?`c`;
??},
};
for?(const?it?of?iterator)?{
??console.log(it);
}
//?a
//?b
//?c
復制代碼
那在異步迭代器中是不是也可以這樣寫?答案當然是肯定的咯,我們只需要在 function 前面加個 async 用來表示是個異步迭代器即可
const?asyncIterator?=?{
??[Symbol.asyncIterator]:?async?function*?()?{
????yield?`a`;
????yield?`b`;
????yield?`c`;
??},
};
(async?()?=>?{
??for?await?(const?x?of?asyncIterator)?{
????console.log(x);
??}
})();
//?a
//?b
//?c
復制代碼
看起來是不是 ojbk ?同樣
function*?it()?{
??yield?`a`;
??yield?`b`;
??yield?`c`;
}
for?(const?i?of?it())?{
??console.log(i);
}
//?a
//?b
//?c
async?function*?ait()?{
??yield?`a`;
??yield?`b`;
??yield?`c`;
}
(async?()?=>?{
??for?await?(const?i?of?ait())?{
????console.log(i);
??}
})();
//?a
//?b
//?c
復制代碼
這個看起來是不是感覺N多語言都有這個?看起來就是標準的 Stream 實現(xiàn),大家都大同小異,這樣也挺好,方便理解嘛,一個懂了,其他的基本也就ok了
總結(jié)
文章到這兒又要和大家說再見了,寥寥幾個特性有的時候真的恨不得寫個幾萬字,因為涉及的東西實在是太多了,不是簡單幾句就能說明白的。但是呢,這個時間它不允許啊,我也就只能含淚和大家說晚安了。
這篇文章也寫了我?guī)滋欤瑢憣戇@,寫寫那兒的,總算也是寫完了。感覺這個 ES9 全是正則相關(guān)的 ??,文章里面我給自己留了一個作業(yè),有空水一篇正則使用的文章。這個正則我相信很多同學都是迷迷糊糊的,我見過不少N年經(jīng)驗的對正則也不甚了解。所以這個正則,我肯定是要水的,盡請期待一下吧,不說包會,至少能夠和別人吹吹牛皮。
如果文章對您有幫助的話,歡迎 點贊 、 評論 、 關(guān)注 、 收藏 、 分享 ,您的支持是我碼字的動力,萬分感謝!!!??
如果文章內(nèi)容出現(xiàn)錯誤的地方,歡迎指正,交流,謝謝??
最后,大家可以 點擊這兒[12] 加入QQ群 FlutterCandies??[13] 和各路大佬們進行交流哦,這里大家說話都超好聽的
關(guān)于本文
來源:盡管如此世界依然美麗
https://juejin.cn/post/7023037816204427272
