第一章、基礎類型
1.布爾值
let isDone: boolean = false;
2.數字
let decLiteral: number = 6;
3.字元串
let name: string = "bob";
4.數組
let list: number[] = [1, 2, 3];
5.任意類型 any
變量的類型是動态的,可以是任意的,并且不會被類型檢查器而檢查,可以順利通過編譯階段
let notSure: any = 4;
notSure = "maybe a string instead";
數組包含不同類型的元素
let list: any[] = [1, true, "free"];
list[1] = 100;
6.無任何類型void
變量沒有任何類型,函數沒有傳回值的時候也可以定義函數為void類型
function warnUser(): void {
console.log("This is my warning message");
}
聲明一個void類型的變量沒有什麼大用,因為你隻能為它賦予undefined和null:
let unusable: void = undefined;
7.Null 和 Undefined
8.元組 Tuple
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
9.Never
never類型表示的是那些永不存在的值的類型。 例如, never類型是那些總是會抛出異常或根本就不會有傳回值的函數表達式或箭頭函數表達式的傳回值類型; 變量也可能是 never類型,當它們被永不為真的類型保護所限制時。
10.對象Object
11.枚舉
enum類型是對JavaScript标準資料類型的一個補充。 像C#等其它語言一樣,使用枚舉類型可以為一組數值賦予友好的名字。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
12.類型斷言(類型轉換)
尖括号
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
as文法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
第二章、變量聲明
var、let、const,同JavaScript
1.var是函數作用域,
let是塊作用域,不允許重複聲明
const是塊作用域且初始化後無法改變其值
函數作用域:函數
塊作用域:if for 等{}語句
2.var會聲明提升,但指派不會提升
console.log(name); //undefined
var name='a'
let,const暫時性死區
console.log(name); //ReferenceError: name沒有定義
let name='a'
第三章、接口
我們傳入的對象參數實際上會包含很多屬性,但是編譯器隻會檢查那些必需的屬性是否存在,并且其類型是否比對。
類型檢查器不會去檢查屬性的順序,隻要相應的屬性存在并且類型也是對的就可以。
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
這個接口代表了有一個 label屬性且類型為string的對象。
1.可選屬性
interface SquareConfig {
color?: string;
width?: number;
}
2.隻讀屬性
interface Point {
readonly x: number;
readonly y: number;
}
3.函數類型
interface SearchFunc {
(source: string, subString: string): boolean;
}
這樣定義後,我們可以像使用其它接口一樣使用這個函數類型的接口。 下例展示了如何建立一個函數類型的變量,并将一個同類型的函數指派給這個變量。
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
對于函數類型的類型檢查來說,函數的參數名不需要與接口裡定義的名字相比對。 比如,我們使用下面的代碼重寫上面的例子:
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
- 函數的參數會逐個進行檢查,要求對應位置上的參數類型是相容的。
- 如果你不想指定參數類型,TypeScript的類型系統會推斷出參數類型,因為函數直接指派給了 SearchFunc類型變量。
- 函數的傳回值類型是通過其傳回值推斷出來的(此例是 false和true)。
如果讓這個函數傳回數字或字元串,類型檢查器會警告我們函數的傳回值類型與 SearchFunc接口中的定義不比對。
4.可索引類型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
第四章、函數
1.函數類型
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };
可以給每個參數和傳回值添加類型,但是傳回值類型可以省略,因為ts可以自動推斷出傳回值類型
2.推斷類型
嘗試這個例子的時候,你會發現如果你在指派語句的一邊指定了類型但是另一邊沒有類型的話,TypeScript編譯器會自動識别出類型:
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };
// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number =
function(x, y) { return x + y; };
3.可選參數和預設參數
注意:可選參數和預設參數一定要放在必須參數的後面(預設參數可以放前面,但是為了保證參數的順序,一定要傳入undefined)
TypeScript裡的每個函數參數都是必須的。傳遞給一個函數的參數個數必須與函數期望的參數個數一緻。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
在TypeScript裡我們可以在參數名旁使用 ?實作可選參數的功能
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
在TypeScript裡,我們也可以為參數提供一個預設值,當使用者沒有傳遞這個參數或傳遞的值是undefined時。 它們叫做有預設初始化值的參數。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right
帶預設初始化的參數都是可選類型的,與可選參數一樣,在調用函數的時候可以省略。
與普通可選參數不同的是,帶預設值的參數不需要放在必須參數的後面。 如果帶預設值的參數出現在必須參數前面,使用者必須明确的傳入 undefined值來獲得預設值。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
4.剩餘參數(同arguments)
必要參數,預設參數和可選參數有個共同點:它們表示某一個參數。 有時,你想同時操作多個參數,或者你并不知道會有多少參數傳遞進來。 在JavaScript裡,你可以使用 arguments來通路所有傳入的參數。在TypeScript裡,你可以把所有參數收集到一個變量裡:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
//Joseph Samuel Lucas MacKinzie
這個省略号也會在帶有剩餘參數的函數類型定義上使用到:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
第五章、泛型
1.初識泛型
不用泛型的話,identity函數可能是下面這樣:
function identity(arg: number): number {
return arg;
}
或者,我們使用any類型來定義函數:
function identity(arg: any): any {
return arg;
}
使用any類型會導緻這個函數可以接收任何類型的arg參數,這樣就會導緻一個錯誤:傳入的類型與傳回的類型應該是相同的,但是如果用any就會導緻不知道傳回什麼類型的值。
是以,我們需要一種方法使傳回值的類型與傳入參數的類型是相同的。那就是泛型(泛:範圍,型:類型。是以泛型可以是任何類型)
function identity<T>(arg: T): T {
return arg;
}
#####更推薦第一種用法
我們定義了泛型函數後,可以用兩種方法使用。 第一種是,傳入所有的參數,包含類型參數:
let output = identity<string>("myString"); // type of output will be 'string'
這裡我們明确的指定了T是string類型,并做為一個參數傳給函數,使用了<>括起來而不是()。
第二種方法更普遍。利用了類型推論 -- 即編譯器會根據傳入的參數自動地幫助我們确定T的類型:
let output = identity("myString"); // type of output will be 'string'
注意我們沒必要使用尖括号(<>)來明确地傳入類型;編譯器可以檢視myString的值,然後把T設定為它的類型。 類型推論幫助我們保持代碼精簡和高可讀性。如果編譯器不能夠自動地推斷出類型的話,隻能像上面那樣明确的傳入T的類型,在一些複雜的情況下,這是可能出現的。
T幫助我們捕獲使用者傳入的類型(比如:number),之後我們就可以使用這個類型。 之後我們再次使用了 T當做傳回值類型。現在我們可以知道參數類型與傳回值類型是相同的了。
我們把這個版本的identity函數叫做泛型,因為它可以适用于多個類型。 不同于使用 any,它不會丢失資訊,像第一個例子那像保持準确性,傳入數值類型并傳回數值類型。
2.使用泛型
如果我們想同時列印出arg的長度。 我們很可能會這樣做:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
這麼做編輯器會報錯,因為arg是任意類型,它不一定具有length屬性,萬一T是number,那就沒有length屬性,但是數組就有length屬性。
是以為了避免出現以上錯誤,我們可以在arg後面指定,arg是T類型中的數組類型
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
這樣做可以讓我們把泛型變量T當做T類型的一部分使用,而不是整個類型,增加了靈活性。
3.泛型類型
泛型函數的類型與非泛型函數的類型沒什麼不同,隻是有一個類型參數在最前面,像函數聲明一樣:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
我們也可以使用不同的泛型參數名,隻要在數量上和使用方式上能對應上就可以。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
下面是泛型和接口的結合,第一個例子,在使用函數的時候需要定義T的類型。第二個例子T的類型直接在使用接口的時候定義好了
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
如果想用接口來描述我們想用T類型中的數組類型
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
注意:現在這個泛型函數被定義了限制,是以它不再是适用于任意類型:
loggingIdentity(3); // Error, number doesn't have a .length property
我們需要傳入符合限制類型的值,必須包含必須的屬性:
loggingIdentity({length: 10, value: 3});
4.泛型工具
1.typeof
在 TypeScript 中,typeof 操作符可以用來擷取一個變量聲明或對象的類型。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem= typeof sem; // -> Person
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
2.keyof
keyof 操作符是在 TypeScript 2.1 版本引入的,該操作符可以用于擷取某種類型的所有鍵,其傳回類型是聯合類型。
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
在 TypeScript 中支援兩種索引簽名,數字索引和字元串索引:
interface StringArray {
// 字元串索引 -> keyof StringArray => string | number
[index: string]: string;
}
interface StringArray1 {
// 數字索引 -> keyof StringArray1 => number
[index: number]: string;
}
3.in
in 用來周遊枚舉類型:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
4.infer
在條件類型語句中,可以用 infer 聲明一個類型變量并且對它進行使用。
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
以上代碼中 infer R 就是聲明一個變量來承載傳入函數簽名的傳回值類型,簡單說就是用它取到函數傳回值的類型友善之後使用。
5.extends
有時候我們定義的泛型不想過于靈活或者說想繼承某些類等,可以通過 extends 關鍵字添加泛型限制。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
現在這個泛型函數被定義了限制,是以它不再是适用于任意類型:
這時我們需要傳入符合限制類型的值,必須包含必須的屬性:
6.Partial
Partial 的作用就是将某個類型裡的屬性全部變為可選項 ?。
定義:
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
在以上代碼中,首先通過 keyof T 拿到 T 的所有屬性名,然後使用 in 進行周遊,将值賦給 P,最後通過 T[P] 取得相應的屬性值。中間的 ? 号,用于将所有屬性變為可選。
示例:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "Learn TS",
description: "Learn TypeScript",
};
const todo2 = updateTodo(todo1, {
description: "Learn TypeScript Enum",
});
在上面的 updateTodo 方法中,我們利用 Partial 工具類型,定義 fieldsToUpdate 的類型為 Partial,即:
{
title?: string | undefined;
description?: string | undefined;
}
第六章、枚舉
感覺沒啥用,不學了
第七章、類型推論
1.基礎
TypeScript裡,在有些沒有明确指出類型的地方,類型推論會幫助提供類型。如下面的例子
變量x的類型被推斷為數字。 這種推斷發生在初始化變量和成員,設定預設參數值和決定函數傳回值時。
2.最佳通用類型
當需要從幾個表達式中推斷類型時候,會使用這些表達式的類型來推斷出一個最合适的通用類型。例如,
為了推斷x的類型,我們必須考慮所有元素的類型。 這裡有兩種選擇: number和null。 計算通用類型算法會考慮所有的候選類型,并給出一個相容所有候選類型的類型。
由于最終的通用類型取自候選類型,有些時候候選類型共享相同的通用類型,但是卻沒有一個類型能做為所有候選類型的類型。例如:
這裡,我們想讓zoo被推斷為Animal[]類型,但是這個數組裡沒有對象是Animal類型的,是以不能推斷出這個結果。 為了更正,當候選類型不能使用的時候我們需要明确的指出類型:
如果沒有找到最佳通用類型的話,類型推斷的結果為聯合數組類型,(Rhino | Elephant | Snake)[]。
3.上下文類型
TypeScript類型推論也可能按照相反的方向進行。 這被叫做“按上下文歸類”。按上下文歸類會發生在表達式的類型與所處的位置相關時。比如:
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- Error
};
這個例子會得到一個類型錯誤,TypeScript類型檢查器使用Window.onmousedown函數的類型來推斷右邊函數表達式的類型。 是以,就能推斷出 mouseEvent參數的類型了。 如果函數表達式不是在上下文類型的位置, mouseEvent參數的類型需要指定為any,這樣也不會報錯了。
如果上下文類型表達式包含了明确的類型資訊,上下文的類型被忽略。 重寫上面的例子:
window.onmousedown = function(mouseEvent: any) {
console.log(mouseEvent.button);
};
這個函數表達式有明确的參數類型注解,上下文類型被忽略。 這樣的話就不報錯了,因為這裡不會使用到上下文類型。
上下文歸類會在很多情況下使用到。 通常包含函數的參數,指派表達式的右邊,類型斷言,對象成員和數組字面量和傳回值語句。 上下文類型也會做為最佳通用類型的候選類型。比如:
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
這個例子裡,最佳通用類型有4個候選者:Animal,Rhino,Elephant和Snake。 當然, Animal會被做為最佳通用類型。
第八章、類型相容性
1.開始
TypeScript結構化類型系統的基本規則是:如果x要相容y,那麼y至少具有與x相同的屬性。比如:
interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;
這裡要檢查y是否能指派給x,編譯器檢查x中的每個屬性,看是否能在y中也找到對應屬性。 在這個例子中,y必須包含名字是name的string類型成員。y滿足條件,是以指派正确。
檢查函數參數時使用相同的規則:
function greet(n: Named) {
console.log('Hello, ' + n.name);
}
greet(y); // OK
注意,y有個額外的location屬性,但這不會引發錯誤。 隻有目标類型(這裡是Named)的成員會被一一檢查是否相容。這個比較過程是遞歸進行的,檢查每個成員及子成員。
2.比較兩個函數
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error
要檢視x是否能指派給y,首先看它們的參數清單。 x的每個參數必須能在y裡找到對應類型的參數。 注意的是參數的名字相同與否無所謂,隻看它們的類型。 這裡,x的每個參數在y中都能找到對應的參數,是以允許指派。
第二個指派錯誤,因為y有個必需的第二個參數,但是x并沒有,是以不允許指派。
下面來看看如何處理傳回值類型,建立兩個僅是傳回值類型不同的函數:
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error, because x() lacks a location property
類型系統強制源函數的傳回值類型必須是目标函數傳回值類型的子類型。
第九章、進階類型
1.交叉類型
Cat & Dog 同時是 Cat 和 Dog 兩個類型的集合,(個人感覺,就像是一個新的類型同僚繼承了兩個類型,A+B=C)
// 這裡會把參數的屬性全指派給result
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) { // 判斷result自身是否包含id屬性
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
var jim = extend(new Cat(), new Dog());
2.聯合類型
聯合類型表示一個值可以是幾種類型之一。 我們用豎線( |)分隔每個類型,是以 number | string 表示一個值可以是 number或string,傳入的參數隻要滿足其中之一就行了。
function padLeft(value: string, padding: string | number ) {
// ...
}
如果一個值是聯合類型,我們隻能通路此聯合類型的所有類型裡共有的成員。(因為萬一通路的那個屬性不存在此類型中就會報錯,是以最好通路共有屬性)
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
這個例子裡, Bird具有一個 fly成員。 我們不能确定一個 Bird | Fish類型的變量是否有 fly方法。 如果變量在運作時是 Fish類型,那麼調用 pet.fly()就出錯了。
3.類型保護與區分類型
如何區分到底是哪種類型呢? 可以使用類型斷言
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
使用者自定義的類型保護
如果多次使用類型斷言,那麼會很麻煩,我們可以封裝一個函數,通過函數來判斷到底是哪個類型
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
//pet is Fish就是類型謂詞。 謂詞為 propsName is Type這種形式, propsName必須是來自于目前函數簽名裡的一個參數名。
// 'swim' 和 'fly' 調用都沒有問題了
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
4.typeof類型保護(注意typeof的使用範圍)
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
5.instanceof類型保護(注意instanceof的使用範圍)
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 類型為SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 類型細化為'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 類型細化為'StringPadder'
}
第十章、聲明合并
1.基礎概念
TypeScript中的聲明會建立以下三種實體之一:命名空間,類型或值。 建立命名空間的聲明會建立一個命名空間,它包含了用(.)符号來通路時使用的名字。 建立類型的聲明是:用聲明的模型建立一個類型并綁定到給定的名字上。 最後,建立值的聲明會建立在JavaScript輸出中看到的值。
了解每個聲明建立了什麼,有助于了解當聲明合并時有哪些東西被合并了。
2.合并接口
合并的機制是把雙方的成員放到一個同名的接口裡。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
非函數成員
- 接口的非函數的成員應該是唯一的。
- 如果它們不是唯一的,那麼它們必須是相同的類型。
- 如果非函數成員同名但是它們的類型不同,則編譯器會報錯。
函數成員
- 每個同名函數聲明都會被當成這個函數的一個重載(函數重載是指在同一作用域内,可以有一組具有相同函數名,不同參數清單的函數)
- 當接口 A與後來的接口 A合并時,後面的接口具有更高的優先級。
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
這三個接口合并成一個聲明:
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
調用clone這個函數的時候,會優先使用第一個聲明
這個規則有一個例外是當出現特殊的函數簽名時。 如果簽名裡有一個參數的類型是 單一的字元串字面量(比如,不是字元串字面量的聯合類型),那麼它将會被提升到重載清單的最頂端。
比如,下面的接口會合并到一起:
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
合并後的 Document将會像下面這樣:
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}