大家好,我是若川。持續組織了近一年的源碼共讀活動,感興趣的可以?加我微信?ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北
籍前端群,可加我微信進群。
2022 年 7 月 19 日至 21 日,由 Google 主辦的 TC39 第 91 次會議在美國舊金山舉行。以下 ECMAscript 提案在該會議上取得了階段性進展:
?? 第四階段:
Hashbang Grammar:允許 JavaScript 文件頭使用
#!
用于 shell 識別解釋器。
🌟 第三階段:
Duplicate named capturing groups:允許正則表達式捕獲組的命名重復。
💫 第二階段:
Import Reflection:建議使用導入反射屬性導入 ES 模塊的語法。
🌙 第一階段:
Symbol Predicates:建議引入區分 symbol 的方法。
Policy Maps and Sets:具有緩存替換策略(如 LRU 和 LFU)的 Maps 和 Sets 提案。
Function Memoization:函數記憶。
Object pick/omit:符合人體工程學的動態對象重構。
TC39 是專門負責定義 JavaScript 標準的技術委員會,根據 ECMAScript 規范對 JavaScript 語言進行了標準化,包括(但不限于)語言的語法、語義、庫和支持該語言的補充技術。?對于新提案,從提出到最后被納入 ECMAScript 新特性,TC39 的規范中分為五步:
stage0(strawman),任何TC39的成員都可以提交。
stage1(proposal),進入此階段就意味著這一提案被認為是正式的了,需要對此提案的場景與API進行詳盡的描述。
stage2(draft),演進到這一階段的提案如果能最終進入到標準,那么在之后的階段都不會有太大的變化,因為理論上只接受增量修改。
state3(candidate),這一階段的提案只有在遇到了重大問題才會修改,規范文檔需要被全面的完成。
state4(finished),這一階段的提案將會被納入到ES每年發布的規范之中。
1、第四階段
(1)Hashbang Grammar
Unix 的命令行腳本都支持#!
命令,又稱為 Hashbang。這個命令放在腳本的第一行,用來指定腳本的執行器。Hashbang Grammar 提案就是想為JavaScript 腳本引入了#!命令,這個命令寫在腳本文件或者模塊文件的第一行:
//?寫在腳本文件的第一行
#!/usr/bin/env?node
'use?strict';
console.log(1);//?寫在模塊文件的第一行
#!/usr/bin/env?node
export?{};
console.log(1);
這樣,Unix 命令行就可以直接執行腳本了:
# 以前執行腳本
node hello.js# 有了 hashbang 之后執行腳本
./hello.js
不過這樣的話,hashbang 就必須嚴格的在文件頭,否則就會出現語法錯誤,導致這個JavaScript腳本文件無法使用。
提案地址:https://github.com/tc39/proposal-hashbang
2、第三階段
(1)Duplicate named capturing groups
在正則表達式中,可以使用捕獲組來對匹配模式中的某一部分做獨立匹配。現在,在 JavaScript 中,正則表達式中的命名捕獲組需要是唯一的。
const?str?=?"2022-07";
const?reg?=?/(?<year>[0-9]{4})-(?<month>[0-9]{2})/;const?group?=?str.match(reg).groups;group.year;??//?'2022'
對于以下正則表達式,即匹配“2022-07”格式和“07-2022”格式:
/(?<year>[0-9]{4})-[0-9]{2}|[0-9]{2}-(?<year>[0-9]{4})/
這是一個錯誤,因為重復使用了名稱year
。但有時想匹配一個可以用多種格式編寫的東西(如上)。能夠在這種情況下使用相同的命名會很好。Duplicate named capturing groups 提案就是為解決這個問題。此提案提出允許捕獲組的命名可以重復,以此來支持上面這種場景。此提案允許在名稱出現在不同|
時重用它們。
提案地址:https://github.com/tc39/proposal-duplicate-named-capturing-groups
3、第二階段
(1)Import Reflection
對于 JavaScript 和 WebAssembly,需要能夠在標準主機執行模型之外更緊密地自定義模塊的加載、鏈接和執行。對于 JavaScript,創建 userland loaders 需要模塊反射類型,以便共享主機解析、執行、安全和緩存語義。
在語法上支持模塊反射作為一種新的導入形式創建了一個原語,可以將模塊的靜態、安全和工具優勢從 ESM 集成擴展到這些動態實例化用例中。
import?module?x?from?"<specifier>";
module
反射類型被添加到 ImportStatement
的開頭。僅支持上述形式,不支持命名導出和未綁定聲明。
動態導入:
const?x?=?await?import("<specifier>",?{?reflect:?"module"?});
對于動態導入,模塊導入反射在指定導入斷言的第二個屬性選項包中使用 reflect 指定。
提案地址:https://github.com/tc39/proposal-import-reflection
4、第一階段
(1)Symbol Predicates
該提案提出了兩個區分 symbol 的方法:
Symbol.isRegistered(symbol)
Symbol.isWellKnown(symbol)
在實際使用時,并非所有 symbol 都是相同的,并且更多地了解它們的含義可能很有用,尤其是對于庫而言。了解 symbol 是否真正獨一無二、可偽造、跨領域共享可能很重要,具體還是要器具與其用例。
可以在庫中檢測 symbol 是否可以用作 WeakMap 鍵:
function?isWeakMapKey(key)?{switch?(typeof?key):?{case?"object":return?key?!==?null;case?"function":return?true;case?"symbol":return?!Symbol.isRegistered(sym);}return?false;
}isWeakMapKey({});?//?true
isWeakMapKey(Symbol());?//?true
isWeakMapKey("foo");?//?false
isWeakMapKey(Symbol.for("foo"));?//?false
isWeakMapKey(Symbol.asyncIterator);?//?true
了解是否獲得了真正獨一無二的 symbol:
const?isUniqueSymbol?=?sym?=>?typeof?sym?===?"symbol"?&&?!(Symbol.isRegistered(sym)?||?Symbol.isWellKnown(sym));isUniqueSymbol(Symbol());?//?true
isUniqueSymbol(Symbol.for("foo"));?//?false
isUniqueSymbol(Symbol.asyncIterator);?//?false
isUniqueSymbol({});?//?false
提案中的兩個方法:
Symbol.isRegistered(value)
:將未知值作為唯一參數,返回布爾值:如果 symbol 已注冊,則返回 true,否則返回 false。Symbol.isWellKnown(value)
:將未知值作為唯一參數,返回布爾值:如果 symbol 是 ECMA262 和 ECMA402 定義的已知的 symbol 之一,則返回 true,否則返回 false。
兩個方法的 Polyfill:
Symbol.isRegistered(symbol)
: https://github.com/inspect-js/is-registered-symbolSymbol.isWellKnown(symbol)
: https://github.com/inspect-js/is-well-known-symbol
提案地址:https://github.com/tc39/proposal-symbol-predicates
(2)Policy Maps and Sets
開發人員經常使用 Map 數據結構作為緩存,并且有時希望限制緩存的內存消耗。建議探索向 JavaScript 語言添加映射數據結構,以支持各種基本的簡單緩存替換策略,例如 LRU(最近最少使用)、LFU(最不常用)、FIFO(先進先出)和 LIFO (后進先出)。
該提案將向全局對象添加幾個內置類。它們中的每一個都有一個可變的類似 Map 的接口或一個可變的類似 Set 的接口。
對于類似 Map 的類,這些包括:
m.size
: m 中的值的數量。m.has(key)
: 返回一個布爾值,m 是否具有給定 key。m.get(key)
:查找指定的 key,如果有則返回 m 中=該 key 對應的的 value,否則返回undefined。m.set(key, value)
:將 m 中key 的值設置為 value,返回m自身。m.delete(key)
:刪除指定 key 及對應的 value(如果有)。返回一個布爾值,表示在刪除之前 m 中是否有該 key。m.clear()
:從 m 中刪除所有內容。返回 undefined。m[Symbol.iterator]():
不確定是否應該實現。m.entries()
: 不確定是否應該實現。m.keys():
不確定是否應該實現。m.values()
: 不確定是否應該實現。m.forEach()
: 不確定是否應該實現。
對于類似 Set 的類,這些包括:
s.size
:s中值的個數。s.has(value)
:返回 s 是否具有給定 value 的布爾值。s.add(value)
:將給定值添加到 s,返回 s 本身。s.delete(key)
:如果 s 有值,則從 s 中刪除給定值。返回在刪除值之前 s 是否具有值的布爾值。s.clear()
:從 s 中刪除所有值,返回 undefined。m[Symbol.iterator]()
:不確定是否應該實現。s.values()
:不確定是否應該實現。s.forEach()
:不確定是否應該實現。
(1)FIFOMap 和 FIFOSet
new?FIFOMap(maxNumOfEntries,?entries?=?[])
new?FIFOSet(maxNumOfValues,?values?=?[])
如果給定非整數最大數量的 entries/values
,或者初始 entries/values
不可迭代,則構造函數將拋出 TypeErrors。
它們的實例按照它們添加的順序逐出 entries/values
,就像 FIFO 隊列一樣。
(2)LIFOMap and LIFOSet
new?LIFOMap(maxNumOfEntries,?entries?=?[])
new?LIFOSet(maxNumOfValues,?values?=?[])
如果為這些構造函數提供了非整數的最大 entries/values
,或者初始 entries/values
不可迭代,則這些構造函數將拋出 TypeErrors。
它們的實例按照添加順序逐出 entries/values
,就像 LIFO 堆棧一樣。
(3)LRUMap and LRUSet
new?LIFOMap(maxNumOfEntries,?entries?=?[])
new?LIFOSet(maxNumOfValues,?values?=?[])
如果為這些構造函數提供了非整數的最大 entries/values
數,或者初始 entries/values
不可迭代,則這些構造函數將拋出 TypeErrors。
(4)LFUMap 和 LFUSet
new?LIFOMap(maxNumOfEntries,?entries?=?[])
new?LIFOSet(maxNumOfValues,?values?=?[])
如果為這些構造函數提供了非整數的最大 entries/values
數,或者初始 entries/values
不可迭代,則這些構造函數將拋出 TypeErrors。
替代解決方案可以向現有的 Map 和 Set 構造函數添加可選參數:
const?cache?=?new?Map(initialEntries,?256,?policyType);
提案地址:https://github.com/tc39/proposal-policy-map-set
(3)Function Memoization
函數記憶是一種常用的技術,它會緩存函數調用的結果,并在再次出現相同的輸入時返回緩存的結果。這些對以下情況會有用:
在時空權衡中優化昂貴的函數調用(例如,階乘、斐波那契數);
緩存狀態→UI 計算(例如,在 React 的 useMemo 中);
確保回調始終返回相同的單例對象;
邏輯編程中的表格;
函數記憶時很有用的。建議探索在 JavaScript 語言中添加一個 memoization API。
Function.prototype.memo
方法將創建一個新函數,該函數對給定參數的每個元組最多調用一次原始函數。對具有相同參數的新函數的任何后續調用都將返回具有這些參數的第一次調用的結果。
function?f?(x)?{?console.log(x);?return?x?*?2;?}const?fMemo?=?f.memo();
fMemo(3);?//?打印?3,返回?6
fMemo(3);?//?不打印,?返回?6
fMemo(2);?//?打印?2,返回?4
fMemo(2);?//?不打印,?返回?4
fMemo(3);?//?不打印,?返回?6
此外,還可以添加一個函數裝飾器版本:@Function.memo
。這將更容易將 memoization
應用于函數聲明:
@Function.memo
function?f?(x)?{?console.log(x);?return?x?*?2;?}
兩個版本都可以使用遞歸函數:
//?原型方法版本
const?getFibonacci?=?(function?(n)?{if?(n?<?2)?{return?n;}?else?{return?getFibonacci(n?-?1)?+getFibonacci(n?-?2);}
}).memo();
console.log(getFibonacci(100));//?函數裝飾器版本
@Function.memo
function?getFibonacci?(n)?{if?(n?<?2)?{return?n;}?else?{return?getFibonacci(n?-?1)?+getFibonacci(n?-?2);}
}
console.log(getFibonacci(100));
結果緩存的實現
開發人員將能夠傳遞一個可選的 cache
參數,此參數必須是具有 .has
、.get
和 .set
方法的類 Map 對象。
(1)元組 keys?
我們可以使用元組作為緩存的 key。每個元組代表對記憶函數的函數調用,元組的格式為#[thisVal, newTargetVal, ...args]
。
對象值將替換為唯一標識該對象的 symbol。(元組不能直接包含對象。memoized 函數的閉包將關閉一個內部 WeakMap,該 WeakMap 將對象映射到它們的 symbol。)
const?cache?=?new?LRUMap(256);
const?f?=?(function?f?(arg0)?{?return?this.x?+?arg0;?}).memo(cache);
const?o0?=?{?x:?'a'?},?o1?=?{?x:?'b'?};
f.call(o0,?0);?//?返回?'a0'
f.call(o1,?1);?//?返回?'b1'
現在緩存是 LRUMap(2) { #[s0, undefined, 0] ? 'a0', #[s1, undefined, 1] ? 'b1' }
,其中 s0 和 s1 是唯一的 symbol。f 的閉包會在內部關閉 WeakMap { o0 ? s0, o1 ? s1 }
。
memo 的默認行為(即不提供緩存參數)是不確定的。它可能只是一個無界的普通 Map。(WeakMaps 不能包含元組作為它們的 key)。
(2)組合 key?
緩存 key 的另一種選擇是組合 key。每個組合 key 代表對記憶函數的函數調用,組合 key 的形式為 CompositeKey(thisVal, newTargetVal, ...args)
。
const?cache?=?new?LRUMap(256);
const?f?=?(function?f?(arg0)?{?return?this.x?+?arg0;?}).memo(cache);
const?o0?=?{?x:?'a'?},?o1?=?{?x:?'b'?};
f.call(o0,?0);?//?返回?'a0'
f.call(o1,?1);?//?返回?'b1'
現在緩存是 LRUMap(2) { compositeKey(o0, undefined, 0) ? 'a0', compositeKey(o1, undefined, 1) ? 'b1' }
。
memo 的默認行為(即不提供緩存參數)是不確定的。它可能只是一個 WeakMap,它能夠包含組合 key 作為它們的 key。
提案地址:https://github.com/tc39/proposal-function-memo
(4)Object pick/omit
該提案旨在實現 Object.pick
,Object.omit
方法,類似于 TypeScript 中的 Pick 和 Omit 工具函數。
先來看看這兩個方法試圖解決什么問題:
在 MouseEvent 上,只需要 'ctrlKey'、'shiftKey'、'altKey'、'metaKey' 事件
有一個 configObject,只需要它的 ['dependencies', 'devDependencies', 'peerDependencies']
從 req.body 中提取 ['name', 'company', 'email', 'password']
有一個 depsObject,需要忽略其中的所有 @internal/packages
需要通過從 ({ ...state.models, ...action.update }) 中刪除 action.deleted 來構造一個 newModelData
從這些例子中可以看出來,對于一個對象,很多時候我們只想要其中的一部分屬性或者不想要其中的一些屬性。如果 JavaScript 對象提供了 Object.pick
,Object.omit
方法,就可以輕松解決上述問題。
有人認為,我們可以實現如下 pick
和 omit
:
const?pick?=?(obj,?keys)?=>?Object.fromEntries(keys.map(k?=>?obj.hasOwnProperty(k)?&&?[k,?obj[k]]).filter(x?=>?x)
);const?omit?=?(obj,?keys)?=>?Object.fromEntries(keys.map(k?=>?!obj.hasOwnProperty(k)?&&?[k,?obj[k]]).filter(x?=>?x)
);
這樣的實現存在以下問題:
不符合人體工程學;
如果選擇解構的方式,它不適用于 pick,或者對于動態值的省略;
解構不能克隆新對象,而 Object.pick 可以;
解構不能從原型中獲取屬性,而 Object.pick 可以;
解構不能動態選擇屬性,而 Object.pick 可以;
解構不能省略一些屬性,沒有這個提議我們只能克隆和刪除。
語法:
Object.pick(obj[,?pickedKeys?|?predictedFunction(currentValue[,?key[,?object]])[,?thisArg])
Object.omit(obj[,?omittedKeys?|?predictedFunction(currentValue[,?key[,?object]])[,?thisArg])
使用方式:
//?default
Object.pick({a?:?1});?//?=>?{}
Object.omit({a?:?1});?//?=>?{a:?1}
Object.pick({a?:?0,?b?:?1},?v?=>?v);?//?=>?{b:?1}
Object.pick({a?:?0,?b?:?1},?v?=>?!v);?//?=>?{a:?0}
Object.pick({},?function?()?{?console.log(this)?});?//?=>?對象本身
Object.pick({},?function?()?{?console.log(this)?},?window);?//?=>?WindowObject.pick({a?:?1,?b?:?2},?['a']);?//?=>?{a:?1}
Object.omit({a?:?1,?b?:?2},?['b']);?//?=>?{a:?1}Object.pick({a?:?1,?b?:?2},?['c']);?//?=>?{}
Object.omit({a?:?1,?b?:?2},?['c']);?//?=>?{a:?1,?b:?2}Object.pick([],?[Symbol.iterator]);?//?=>?{Symbol(Symbol.iterator):?f}
Object.pick([],?['length']);?//?=>?{length:?0}Object.pick({a?:?1,?b?:?2},?v?=>?v?===?1);?//?=>?{a:?1}
Object.pick({a?:?1,?b?:?2},?v?=>?v?!==?2);?//?=>?{a:?1}
Object.pick({a?:?1,?b?:?2},?(v,?k)?=>?k?===?'a');?//?=>?{a:?1}
Object.pick({a?:?1,?b?:?2},?(v,?k)?=>?k?!==?'b');?//?=>?{a:?1}
提案地址:https://github.com/tc39/proposal-object-pick-or-omit
我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
如何準備20K+的大廠前端面試
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 lxchuan12、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 lxchuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~