最近遇見一個不錯的 TS 面試題,分享一下。
這題有 3 個層次,我們一層層來看。
第一層的要求是這樣的:
實作一個zip 函數,對兩個陣列的元素依序兩兩合併,例如輸入[1,2,3] , [4,5,6] 時,返回[[1,4], [2,5],[3,6]]
這層就是每次各從兩個陣列取一個元素,合併之後放到陣列裡,然後繼續處理下一個,遞歸進行這個流程,直到陣列為空即可。
function zip(target, source) { if (!target.length || !source.length) return []; const [one, ...rest1] = target; const [other, ...rest2] = source; return [[one, other], ...zip(rest1, rest2)]; }
結果是對的:
第一層還是比較簡單的,然後我們來看第二層要求:
#給這個zip 函數定義ts 類型(兩種寫法)
函數的定義有兩種形式:
#直接透過function 宣告函數:
function func() {}
和宣告匿名函數接著賦值給變數:
const func = () => {}
而參數和傳回值的型別都是數組,只是具體型別不知道,可以寫unknown[]。
所以兩個函數類型的定義就是這樣的:
#也是直接function 宣告函數類型和interface 聲明函數類型接著加到變數類型上兩種。
因為具體元素類型不知道,所以用 unknown。
這裡可能會問any 和unknown 的差別:
any 和unknown 都可以接收任何型別:
但是any 也可以賦值給任何類型,但unknown 不行。
這裡只是用來接收其他類型, 所以 unknown 比any 更合適一些,更安全。
這一層也是比較基礎的ts 語法,第三層就上了難度了:
用型別程式實作精確的型別提示,例如參數傳入[1, 2,3], [4,5,6],那回傳值的型別要提示出[[1,4], [2,5],[3,6]]
這裡要求回傳值類型是精確的,我們就要根據參數的類型來動態產生回傳值類型。
也就是這樣:
宣告兩個型別參數 Target、Source,約束為 unknown[],也就是元素型別任意的陣列型別。
這兩個類型參數分別是傳入的兩個參數的型別。
傳回值透過 Zip 計算得出。
接著要實現 Zip 的高階類型:
傳入的型別參數分別是兩個陣列類型,我們同樣要從中提取每個元素合併在一起。
提取元素可以用模式匹配的方式:
所以這個類型就可以這樣定義:
type Zip<One extends unknown[], Other extends unknown[]> = One extends [infer OneFirst,...infer Rest1] ? Other extends [infer OtherFirst, ...infer Rest2] ? [[OneFirst, OtherFirst], ...Zip<Rest1, Rest2>] : [] : [];
分別擷取兩個陣列的第一個元素,建構成新陣列。然後對剩下的數組遞歸進行這樣的處理,直到數組為空。
這樣就實現了我們想要的高階類型:
但你把它加到函數上會錯誤是回傳值:
因為宣告函數的時候都不知道參數是啥,自然計算不出Zip
#那該怎麼辦?
可以用函數重載解決:
ts 支援函數重載,可以寫多個同名函數的類型的類型定義,最後寫函數的實現,這樣用到這個函數的時候會根據參數的型別來匹配函數型別。
我們用了類型程式設計的那個函數透過這種方式寫就不會報錯了。
我們使用下來看看:
咋回傳值的型別不對呢?
其實這時候匹配的函數型別是對的,只不過推導出的不是字面量類型。
這時候可以加個 as const。
但加上as const 會推導出readonly [1,2,3]
這樣型別就不符合了,所以要在型別參數的宣告上也加上readonly:
但這樣Zip 函數的型別又不匹配了。
難道要把所有用到這個類型的地方都加上 readonly 麼?
不用,我們 readonly 的修飾去掉不就行了?
Typescript 有內建的高階類型readonly:
#可以把索引類型的每個索引都加上readonly 修飾:
但沒有提供去掉readonly 修飾的高階類型,我們可以自己實作:
用對映類型的語法建構個新索引類型,加上個-readonly 就是去掉readonly 修飾的意思。
有的同學可能問了,陣列型別也是索引型別?
是,索引類型是聚合多個元素的類型,所以物件、陣列、class 都是。
所以我們把它用在數組上自然也是可以的:
#(準確來說叫元組,元組是元素個數固定的陣列)
那我們只要在傳入Zip 之前,用Mutable 去掉readonly 就可以了:
再來試試看:
##大功告成!現在傳回值的類型就對了。
如果推導不出字面量類型,應該要匹配這個呀:
但實際上它匹配的還是第一個:
#這時候其實只要調換下兩個函數型別的順序就可以了:
這時字面量參數的情況依然也是對的:
為什麼呢? ######因為###重載函數的型別是從上到下依序匹配,只要匹配到一個就應用。 #########非字面量的情況,型別是 number[],能符合 unknown[] 的那個型別,所以那個函數型別生效了。 ###############而字面量的情況,推導出的是readonly [1,2,3],帶有readonly 所以不匹配unknown[],繼續往下匹配,就匹配到了帶有類型參數的那個函數類型。 ######這樣兩種情況就都應用了合適的函數型別。 ######全部程式碼是這樣的:###type Zip<One extends unknown[], Other extends unknown[]> = One extends [ infer OneFirst, ...infer Rest1 ] ? Other extends [infer OtherFirst, ...infer Rest2] ? [[OneFirst, OtherFirst], ...Zip<Rest1, Rest2>] : [] : []; type Mutable<Obj> = { -readonly [Key in keyof Obj]: Obj[Key]; }; function zip(target: unknown[], source: unknown[]): unknown[]; function zip<Target extends readonly unknown[], Source extends readonly unknown[]>( target: Target, source: Source ): Zip<Mutable<Target>, Mutable<Source>>; function zip(target: unknown[], source: unknown[]) { if (!target.length || !source.length) return []; const [one, ...rest1] = target; const [other, ...rest2] = source; return [[one, other], ...zip(rest1, rest2)]; } const result = zip([1, 2, 3] as const, [4, 5, 6] as const); const arr1 = [1, 2, 3]; const arr2 = [4, '5', 6]; const result2 = zip(arr1, arr2);
总结
今天我们做了一道综合的 ts 面试题,一共有三层:
第一层实现 js 的逻辑,用递归或者循环都能实现。
第二层给函数加上类型,用 function 声明类型和 interface 声明函数类型两种方式,参数和返回值都是 unknown[]。
第三层是用类型编程实现精准的类型提示,这一层需要拿到参数的类型,通过提取元素的类型并构造出新的数组类型返回。还要通过函数重载的方式来声明类型,并且要注意重载类型的声明顺序。
as const 能够让字面量推导出字面量类型,但会带有 readonly 修饰,可以自己写映射类型来去掉这个修饰。
其实这也是我们学习 ts 的顺序,我们先要能把 js 逻辑写出来,然后知道怎么给函数、class 等加 ts 类型,之后学习类型编程,知道怎么动态生成类型。
其中类型编程是 ts 最难的部分,也是最强大的部分。攻克了这一层,ts 就可以说学的差不多了。
【相关推荐:javascript学习教程
以上是分享一個不錯的TS面試題(含3層),看看能答到第幾層!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版
中文版,非常好用

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)