自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我讀 Typescript 源碼的秘訣都在這里了

開(kāi)發(fā) 前端
T extends boolean 這部分是一個(gè) ConditionType,有 checkType、extendsType、trueType、falseType 四個(gè)屬性分別代表不同的部分。

 [[427265]]

這篇文章整理了我是怎么讀 Typescript 源碼的,類(lèi)似的技巧也可以用于其他庫(kù)的源碼閱讀。

先從一個(gè) ts 的語(yǔ)法開(kāi)始:

Test<T> 這個(gè)高級(jí)類(lèi)型,有一個(gè)泛型參數(shù) T,當(dāng) T 傳入的類(lèi)型為聯(lián)合類(lèi)型的時(shí)候,有兩種情況:

  • 如果 checkType(extends 左邊的類(lèi)型) 是 T,則把聯(lián)合類(lèi)型拆開(kāi)后解析類(lèi)型,最后合并成一個(gè)聯(lián)合類(lèi)型返回。
  • 如果 checkType 不是 T,把聯(lián)合類(lèi)型整體作為 T 來(lái)解析,返回解析后的類(lèi)型。

這個(gè)語(yǔ)法叫 Distributive Condition Type,分布式條件類(lèi)型。設(shè)計(jì)的目的就是為了簡(jiǎn)化 Test<number> | Test<boolean> 的情況。

這里不談這個(gè)語(yǔ)法設(shè)計(jì)的怎么樣,我們通過(guò)這個(gè)語(yǔ)法的實(shí)現(xiàn)作為抓手,來(lái)探究一下 ts 源碼應(yīng)該怎么讀。

類(lèi)型的表示法:類(lèi)型對(duì)象

ts 會(huì)把源碼進(jìn)行 parse,生成 AST,然后從 AST 中解析出類(lèi)型信息。

ts 的類(lèi)型信息是通過(guò)類(lèi)型對(duì)象來(lái)存儲(chǔ)的,我們來(lái)看幾個(gè)例子。(可視化的查看 AST 可以使用 astexplorer.net 這個(gè)網(wǎng)站。)

上面定義了四個(gè)類(lèi)型:

a 類(lèi)型是 LiteralType,字面量類(lèi)型,literal 屬性保存具體的字面量,這里是 NumericLiteral,數(shù)字字面量。

b 類(lèi)型是 UnionType,聯(lián)合類(lèi)型,types 屬性保存了它所包含的類(lèi)型,這里是兩個(gè) LiteralType

T extends boolean 這部分是一個(gè) ConditionType,有 checkType、extendsType、trueType、falseType 四個(gè)屬性分別代表不同的部分。

可以看到,T 是一個(gè) TypeReference 類(lèi)型,也就是它只是一個(gè)變量引用,具體的值還是泛型參數(shù)傳入的類(lèi)型。

Test<number | boolean> 也是一個(gè) TypeReference,類(lèi)型引用。有 typeName 和 typeArguments 兩個(gè)屬性,typeName 就是它引用的類(lèi)型 Test,typeArguments 就是泛型參數(shù)的值,這里是 UnionType。

所以說(shuō),類(lèi)型在 ts 里面都是通過(guò)類(lèi)型對(duì)象來(lái)表示的。

比較特別的是 TypeReference 類(lèi)型,它只是一個(gè)引用,具體的類(lèi)型還得把類(lèi)型參數(shù)傳入所引用的類(lèi)型,然后求出最終類(lèi)型。比如這里的 Test<number | boolean> 的類(lèi)型,最終的類(lèi)型是把參數(shù) number | boolean 傳入定義的那個(gè) ConditionType 來(lái)求出的。這就是 ts 的高級(jí)類(lèi)型。

理解了類(lèi)型是怎么表示的,高級(jí)類(lèi)型和泛型參數(shù)都是什么,接下來(lái)我們就可以正式通過(guò)調(diào)試 ts 源碼來(lái)看下 ConditionType 的解析過(guò)程了。

VSCode 調(diào)試 Typescript 源碼

首先,我們要把 ts 源碼下載下來(lái)(加個(gè) depth=1 可以下載單 commit,速度比較快)

  1. git clone --depth=1 git@github.com:microsoft/TypeScript.git 

然后可以看到 lib 目錄下有 tsc.js 和 typescript.js,這兩個(gè)分別是 ts 的命令行和 api 的入口。

但是,這些是編譯以后的 js 代碼,源碼在 src 下,是用 ts 寫(xiě)的。

怎么把編譯后的 js 代碼和 ts 源碼關(guān)聯(lián)起來(lái)呢?sourcemap!

默認(rèn)編譯出來(lái)的產(chǎn)物是沒(méi)有 sourcemap 的,我們要修改下編譯配置:

修改下 src/tsconfig-library-base.json,(這是 ts 生成 lib 代碼的編譯配置)把 sourceMap 改為 true。

之后再編譯源碼:

  1. yarn  
  2. yarn run build:compiler 

然后就可以看到多了一個(gè) built 目錄,下面有 tsc.js、typescript.js 這兩個(gè)入口文件,而且也有了 sourcemap:

接下來(lái)就可以直接調(diào)試 ts 源碼,而不是編譯后的 js 代碼了。信么?

不信我們來(lái)試試。

vscode 直接調(diào)試 ts

vscode 在項(xiàng)目根目錄下的 .vscode/launch.json 下保存調(diào)試配置:

我們添加一個(gè)調(diào)試配置:

  1.     "name""調(diào)試 ts 源碼"
  2.     "program""${workspaceFolder}/built/local/tsc.js"
  3.     "request""launch"
  4.     "skipFiles": [ 
  5.         "<node_internals>/**" 
  6.     ], 
  7.     "args": [ 
  8.         "./input.ts" 
  9.     ], 
  10.     "stopOnEntry"true
  11.     "type""node" 

含義如下:

  • name:調(diào)試配置的名字
  • program:調(diào)試的目標(biāo)程序的地址
  • request:有 launch 和 attch 兩個(gè)取值,代表啟動(dòng)新的還是連上已有的
  • skipFiles:調(diào)試的時(shí)候跳過(guò)一些文件,這里配置的是跳過(guò) node 內(nèi)部的那些文件,調(diào)用棧會(huì)更簡(jiǎn)潔
  • args:命令行參數(shù)
  • stopOnEntry:是否在首行加個(gè)斷點(diǎn)
  • type:調(diào)試的類(lèi)型,這里是用 node 來(lái)跑

保存之后就可以在調(diào)試面板看到該調(diào)試選項(xiàng):

這里我們?cè)O(shè)計(jì)的 input.ts 是這樣的:

  1. type Test<T> = T extends boolean ? "Y" : "N"
  2.  
  3. type res = Test<number | boolean>; 

在 ts 的 checker.ts 部分打個(gè)斷點(diǎn),然后點(diǎn)擊啟動(dòng)調(diào)試。

然后,看,這斷住的地方,就是 ts 源碼啊,不是編譯后的 js 文件。這就是 sourcemap 的作用。

還可以在左邊文件樹(shù)看到源碼的目錄結(jié)構(gòu),這比調(diào)試編譯后的 js 代碼爽多了。

會(huì)了通過(guò) sourcemap 調(diào)試源碼之后,我們?cè)撨M(jìn)入主題了:通過(guò)源碼探究分布式條件類(lèi)型的實(shí)現(xiàn)原理。

其實(shí)我們上面使用的是 tsc.js 的命令行入口來(lái)調(diào)試的,這樣其實(shí)代碼比較多,很難理清要看哪部分代碼。怎么辦呢?

接下來(lái)就是我的秘密武器了,用 typescript compiler api。

typescript compiler api

ts 除了命令行工具的入口外,也提供了 api 的形式,只是我們很少用。但它對(duì)于探究 ts 源碼實(shí)現(xiàn)有很大的幫助。

我們定義個(gè) test.js 文件,引入 typescript 的包:

  1. const ts = require("./built/local/typescript"); 

然后用 ts 的 api 傳入編譯配置,并 parse 源碼成 ast:

  1. const filename = "./input.ts"
  2. const program = ts.createProgram([filename], { 
  3.     allowJs: false 
  4. }); 
  5. const sourceFile = program.getSourceFile(filename); 

這里的 createProgram 第二個(gè)參數(shù)是編譯配置,我傳入了個(gè) allowJS 意思了一下。

program.getSourceFile 返回的就是 ts 的 AST。

并且還可以拿到 typeChecker:

  1. const typeChecker = program.getTypeChecker(); 

然后呢?typeChecker 是類(lèi)型檢查的 api,我們可以遍歷 AST 找到檢查的 node,然后調(diào)用 checker 的 api 進(jìn)行檢查:

  1. function visitNode(node) { 
  2.     if (node.kind === ts.SyntaxKind.TypeReference)  { 
  3.         const type = typeChecker.getTypeFromTypeNode(node); 
  4.  
  5.         debugger; 
  6.     } 
  7.  
  8.     node.forEachChild(child => 
  9.         visitNode(child) 
  10.     ); 
  11.  
  12. visitNode(sourceFile); 

我們判斷了如果 AST 是 TypeReference 類(lèi)型,則用 typeChecker.getTypeFromTypeNode 來(lái)解析類(lèi)型。

接下來(lái)就可以精準(zhǔn)的調(diào)試該類(lèi)型解析的邏輯了,相比命令行的方式來(lái)說(shuō),更方便理清邏輯。

完整代碼如下:

  1. const ts = require("./built/local/typescript"); 
  2.  
  3. const filename = "./input.ts"
  4. const program = ts.createProgram([filename], { 
  5.     allowJs: false 
  6. }); 
  7. const sourceFile = program.getSourceFile(filename); 
  8. const typeChecker = program.getTypeChecker(); 
  9.  
  10. function visitNode(node) { 
  11.     if (node.kind === ts.SyntaxKind.TypeReference)  { 
  12.         const type = typeChecker.getTypeFromTypeNode(node); 
  13.  
  14.         debugger; 
  15.     } 
  16.  
  17.     node.forEachChild(child => 
  18.         visitNode(child) 
  19.     ); 
  20.  
  21. visitNode(sourceFile); 

我們改下調(diào)試配置,然后開(kāi)始調(diào)試:

  1.     "name""調(diào)試 ts 源碼"
  2.     "program""${workspaceFolder}/test.js"
  3.     "request""launch"
  4.     "skipFiles": [ 
  5.         "<node_internals>/**" 
  6.     ], 
  7.     "args": [ 
  8.     ], 
  9.     "type""node" 

在 typeChecker.getTypeFromTypeNode 這行打個(gè)斷點(diǎn),我們?nèi)タ聪戮唧w的類(lèi)型解析過(guò)程。

然后,XDM,打起精神,本文的高潮部分來(lái)了:

我們進(jìn)入了 getTypeFromTypeNode 方法,這個(gè)方法就是根據(jù) AST 的類(lèi)型來(lái)做不同的解析,返回類(lèi)型對(duì)象的。各種類(lèi)型解析的邏輯都是從這里進(jìn)入的,這是一個(gè)重要的交通樞紐。

然后我們進(jìn)入了 TypeReference 的分支,因?yàn)?Test<number | boolean> 就是一個(gè)類(lèi)型引用嘛。

TypeReference 的類(lèi)型就是它引用的類(lèi)型,它引用了 ConditionType,所以會(huì)再解析 T extends boolean 這個(gè) ConditionType 的類(lèi)型:

所有的類(lèi)型都是按照 ast node 的 id 存入一個(gè) nodeLinks 的 map 中來(lái)緩存,只有第一次需要解析,之后直接拿結(jié)果。比如上圖的 resolvedType 就存入了 nodeLinks 來(lái)緩存。

然后,XDM,看到閃閃發(fā)光的那行代碼了么?

解析 ConditionType 的類(lèi)型的時(shí)候會(huì)根據(jù) checkType 部分是否是類(lèi)型參數(shù)(TypeParameter,也就是泛型)來(lái)設(shè)置 isDistributive 屬性。

之后解析 TypeReference 類(lèi)型的時(shí)候,會(huì)傳入具體的類(lèi)型來(lái)實(shí)例化:

這里就判斷了 conditionType 的 isDistributive 屬性,如果是,則把 unionType 的每個(gè)類(lèi)型分別傳入來(lái)解析,最后合并返回。

如圖,我們走到了 isDistributive 為 true 的這個(gè)分支。

那么解析出的類(lèi)型就是 'Y' | 'N' 的聯(lián)合類(lèi)型。

那我們把 input.ts 代碼改一下呢:

  1. type Test<T> = [T] extends [boolean] ? "Y" : "N"
  2.  
  3. type res = Test<number | boolean>; 

checkType 不直接寫(xiě)類(lèi)型參數(shù) T 了。

再跑一次:

這次沒(méi)進(jìn)去了。

難道說(shuō)?

確實(shí),這樣的結(jié)果就是 N。

說(shuō)明了什么?說(shuō)明了 ConditionType 是根據(jù) checkType 是否是類(lèi)型參數(shù)來(lái)設(shè)置了 isDistributive 屬性,之后解析 TypeReference 的時(shí)候根據(jù) isDistributive 的值分別做了不同的解析。

那么只要 checkType 不是 T 就行了。

所以這樣也行:

這樣也行:

我們經(jīng)常用 [T] 來(lái)避免 distributive 只不過(guò)這樣比較簡(jiǎn)潔,看完源碼我們知道了,其實(shí)別的方式也行。

就這樣,我們通過(guò)源碼理清了這個(gè)語(yǔ)法的實(shí)現(xiàn)原理。

總結(jié)

我們以探究 distributive condition type 的實(shí)現(xiàn)原理為目的來(lái)閱讀了 typescript 源碼。

首先把 typescript 源碼下載下來(lái),然后改下編譯配置,生成帶有 sourcemap 的代碼,之后在 vscode 里調(diào)試,這樣可以直接調(diào)試編譯前的源碼,信息更多。

typescript 有 cli 和 api 兩種入口,用 cli 的方式無(wú)關(guān)代碼太多,比較難理清,所以我們用 api 的方式來(lái)寫(xiě)了一段測(cè)試代碼,之后打斷點(diǎn)來(lái)調(diào)試。

ts 的類(lèi)型信息保存在類(lèi)型對(duì)象中,這個(gè)可以用 astexplorer.net 來(lái)可視化的查看。

用 typeChecker.getTypeFromTypeNode 可以拿到某個(gè)類(lèi)型的具體值,我們就是通過(guò)這個(gè)作為入口來(lái)探究各種類(lèi)型的解析邏輯。

源碼中比較重要的有這么幾點(diǎn):

  • getTypeFromTypeNode 方法是通過(guò) node 獲取類(lèi)型的入口方法,所有 AST 的類(lèi)型對(duì)象都是通過(guò)這個(gè)方法拿到
  • nodeLinks 保存了解析后的類(lèi)型,key 為 node id,這樣解析一遍就好了,下次拿緩存。

之后我們看了 ConditionType 的解析邏輯會(huì)根據(jù) checkType 是否為類(lèi)型參數(shù)來(lái)設(shè)置 isDistributive 屬性,然后 TypeReference 實(shí)例化該類(lèi)型的時(shí)候會(huì)根據(jù) isDistributive 的值進(jìn)入不同的處理邏輯,這就是它的實(shí)現(xiàn)原理。

理解了原理之后,我們?cè)偈褂?distributive condition type 就心里有底了,還可以創(chuàng)造很多變形使用,不局限于 [T]。

本文以調(diào)試一個(gè)類(lèi)型解析邏輯的原理為抓手探究了 ts 源碼閱讀方式,調(diào)試 ts 別的部分的代碼,或者調(diào)試其他的庫(kù)也是類(lèi)似的。

希望可以幫助大家掌握 typescript 源碼調(diào)試技巧,想探究某個(gè)類(lèi)型語(yǔ)法實(shí)現(xiàn)原理的時(shí)候,可以通過(guò)源碼層面來(lái)徹底搞清楚。源碼面前,沒(méi)有秘密。

 

責(zé)任編輯:武曉燕 來(lái)源: 神光的編程秘籍
相關(guān)推薦

2017-10-24 14:57:58

AI人工智能機(jī)器學(xué)習(xí)

2018-03-19 14:43:28

2023-09-11 08:51:23

LinkedList雙向鏈表線程

2023-12-11 21:59:01

時(shí)序分析深度學(xué)習(xí)自回歸模型

2019-07-21 08:10:21

技術(shù)研發(fā)優(yōu)化

2019-12-04 07:57:22

6G5G網(wǎng)絡(luò)

2017-08-28 16:40:07

Region切分觸發(fā)策略

2017-12-08 10:42:49

HBase切分細(xì)節(jié)

2022-03-02 10:36:37

Linux性能優(yōu)化

2020-10-12 14:00:52

美的集團(tuán)永洪科技

2017-02-24 12:29:20

Android Thi開(kāi)發(fā)板硬件

2018-11-28 10:39:01

5G網(wǎng)絡(luò)運(yùn)營(yíng)商

2024-07-02 11:16:21

2018-04-26 16:15:02

數(shù)據(jù)庫(kù)MySQLMySQL 8.0

2021-07-01 09:00:00

安全數(shù)字化轉(zhuǎn)型滲透

2022-11-28 08:44:46

死鎖面試線程

2019-12-31 10:08:35

架構(gòu)模式軟件

2018-03-31 08:45:52

iPhone交通卡iOS 11.3

2016-05-20 11:26:54

客戶端優(yōu)化 直播推流

2016-05-20 11:14:55

內(nèi)容緩存 傳輸策略優(yōu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)