TypeScript 聲明合并
TypeScript 編譯器會將程序中多個具有相同名稱的聲明合并為一個聲明。
但這并不是說 TypeScript 會隨意的合并兩個名稱相同的字符串變量,這顯然是不符合語法規定的,那么本節將介紹什么樣的聲明可以進行合并。
1. 慕課解釋
TypeScript 中的聲明會創建以下三種實體之一:命名空間、類型或值。
來看以下聲明都創建了什么實體:
聲明類型 | 創建了命名空間 | 創建了類型 | 創建了值 |
---|---|---|---|
Namespace | √ | √ | |
Class | √ | √ | |
Enum | √ | √ | |
Interface | √ | ||
Type Alias | √ | ||
Function | √ | ||
Variable | √ |
2. 合并接口
最簡單也最常見的聲明合并類型是接口合并。
interface Box {
height: number
width: number
}
interface Box {
scale: number
width: number // 類型相同 OK
}
let box: Box = {height: 5, width: 6, scale: 10}
接口合并,則接口的非函數的成員須是唯一的,哪怕不唯一,最起碼也要類型相同。但如果類型不同,則編輯器報錯。
對于函數成員,每個同名函數聲明都會被當成這個函數的一個重載,后面的接口具有更高優先級。
接口合并時,將遵循以下規范:
- 接口內優先級是從上到下;
- 后面的接口具有更高優先級;
- 如果函數的參數是字符串字面量,會被提升到函數聲明的最頂端。
interface Document {
createElement(tagName: any): Element // 5
}
interface Document {
createElement(tagName: 'div'): HTMLDivElement // 2
createElement(tagName: 'span'): HTMLSpanElement // 3
}
interface Document {
createElement(tagName: string): HTMLElement // 4
createElement(tagName: 'canvas'): HTMLCanvasElement // 1
}
按照上面介紹的規則,得到合并后的聲明:
interface Document {
createElement(tagName: 'canvas'): HTMLCanvasElement
createElement(tagName: 'div'): HTMLDivElement
createElement(tagName: 'span'): HTMLSpanElement
createElement(tagName: string): HTMLElement
createElement(tagName: any): Element
}
3. 合并命名空間
合并多個具有相同名稱的命名空間:
- 導出成員不可重復定義
- 非導出成員僅在其原有的(合并前的)命名空間內可見
namespace A {
let used = true
export function fn() {
return used
}
}
namespace A {
export function fnOther() {
return used // Error, 未找到變量 used
}
}
A.fn() // OK
A.fnOther() // OK
代碼解釋:
第一個命名空間內的非導出成員 used
僅在第一個命名空間內可見。 命名空間對象 A
可以分別訪問在第一個或第二個聲明的導出成員。
4. 命名空間與其它類型的合并
只要命名空間的定義符合將要合并類型的定義,命名空間就可以與其它類型的聲明進行合并,合并結果包含兩者的聲明類型。
4.1 命名空間與類的合并
合并名稱相同的命名空間與類:
- 命名空間內的成員必須導出,合并后的類才能訪問
- 命名空間內導出的成員,相當于合并后類的靜態屬性
- 命名空間要放在類的定義后面
class Album {
label!: Album.AlbumLabel
}
namespace Album {
export class AlbumLabel { }
export const num = 10
}
console.log(Album.num) // 10
注意: 命名空間要放在類的定義后面,命名空間內導出的成員,相當于合并后類的靜態屬性。
4.2 命名空間與函數的合并
- 名稱相同的命名空間與函數掛載同一個對象
- 命名空間要放在函數的定義后面
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix
}
namespace buildLabel {
export let suffix = '.C'
export let prefix = 'Hello, '
}
console.log(buildLabel('Mr.Pioneer')) // Hello, Mr.Pioneer.C
命名空間和函數可以進行合并,是因為在 JavaScript 中函數也是對象。
4.3 命名空間與枚舉的合并
命名空間可以用來擴展枚舉型:
enum Color {
red = 1,
green = 2,
blue = 4
}
namespace Color {
export function mixColor(colorName: string) {
switch (colorName) {
case 'yellow':
return Color.red + Color.green
case 'white':
return Color.red + Color.green + Color.blue
default:
break
}
}
}
console.log(Color.mixColor('yellow')) // 3
解釋: 枚舉本身也是個對象,與命名空間對象合并后對象的屬性進行了擴充。
5. 小結
雖然 TypeScript 有聲明合并的功能,但是在工作中應盡量避免定義命名相同的聲明,合理使用模塊來規避這類情況。