TypeScript學習筆記
《零》,概述
※,vscode 的智能提示用的是 TypeScript language service,這個服務有個叫 AutoAutomatic Type Acquisition(ATA)的東西,ATA會根據package.json中列出的npm modules拉取這些模塊的類型聲明文件(npm Type Declaration files,即 *.d.ts 文件),從而給出智能提示。
※,安裝TypeScript 編譯器: npm install -g typescript(全局安裝) 或 npm install --save-dev typesript (僅將ts編譯器安裝在當前項目中)。如果想裝特定版本的Ts可以使用npm install -g typescript@3.3.1
※,tsc -h 查看幫助文檔。各種編譯選項參數和其含義。
※,tsc --init 可以生成一個tsconfig.json配置文件(當然也可以手動創建此文件)。此文件配置了TypeScript項目信息,如編譯選項, 包含的文件等等。沒有此文件則使用默認設置。使用tsc命令編譯ts文件時,ts編譯器會自動查找 tsconfig.json,并按照其中的配置編譯ts文件。
※,tsc xxx.ts用于編譯某個ts文件,也可以直接運行tsc命令,編譯所有的ts文件。如果有tsconfig.json文件,直接運行tsc將會編譯tsconfig.json中包含的所有ts文件。
※,tsconfig.json中的一些配置項說明:
{
"compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "out",//此屬性配置了編譯后js文件存儲的位置
"sourceMap":true, //true表示編譯后同時生成map文件,指示ts和js文件的對應關系。
},
"files":{
},
"extends":{
},
.....
}※,
《一》,文檔學習 https://www.tslang.cn/docs/home.html
一,快速入門
1,要注意的是盡管 ts 中有錯誤(類型不匹配等),相應的js文件還是被創建了。 就算你的代碼里有錯誤,你仍然可以使用TypeScript。但在這種情況下,TypeScript會警告你代碼可能不會按預期執行。
2,在TypeScript里,只要兩個類型內部的結構兼容那么這兩個類型就是兼容的。 這就允許我們在實現接口時候只要保證包含了接口要求的結構就可以(實現時可以比接口定義的字段多,但是不能比其少),而不必明確地使用 implements語句。當然寫上implements語句更清晰
3,類:它帶有一個構造函數和一些公共字段,要注意的是,在構造函數的參數上使用public等同于創建了同名的成員變量。
二,基礎類型:
1,數字:和JavaScript一樣,TypeScript里的所有數字都是浮點數。 這些浮點數的類型是 number。
2,字符串:可以使用模版字符串,它可以定義多行文本和內嵌表達式。 這種字符串是被反引號包圍( `),并且以${ expr }這種形式嵌入表達式。如 `my name is ${myName}, I am ${age+11} years old`
2.1,JavaScript 中的String與string的區別:String是構造函數,而"string"是變量的一種類型.
JS的數據類型一般用大寫的String,Number,Undefined,Null,Object來表示,而小寫的類型的作用僅僅是在使用 typeof 和 instanceof 用來判斷具體類型,或是作為返回的字符串,用來表明該類型是什么,是基本類型還是引用類型,其他地方就用不到了。 https://blog.csdn.net/fengwei4618/article/details/77955261
typeof String // "function" typeof string // "undefined" typeof "string" // "string"
3,數組:
TypeScript像JavaScript一樣可以操作數組元素。 有兩種方式可以定義數組。 第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個數組:
let list: number[] = [1, 2, 3];
第二種方式是使用數組泛型,Array<元素類型>:
let list: Array<number> = [1, 2, 3];
4,元組 Tuple
元組類型允許表示一個已知元素數量和類型的數組,各元素的類型不必相同。 比如,你可以定義一對值分別為 string和number類型的元組。
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
當訪問一個已知索引的元素,會得到正確的類型:
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
當訪問一個越界的元素,會使用聯合類型替代:
x[3] = 'world'; // OK, 字符串可以賦值給(string | number)類型
console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString
x[6] = true; // Error, 布爾不是(string | number)類型
聯合類型是高級主題,我們會在以后的章節里討論它。
5,枚舉
6,Any
7,Void
8,Null 和Undefined: Null是沒有在內存中開辟空間,Undefined是開辟了空間但是里面沒有存值。
9,Never
10,Object:object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined之外的類型。
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
11,類型斷言
類型斷言好比其它語言里的類型轉換。
類型斷言有兩種形式。 其一是“尖括號”語法:
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;
兩種形式是等價的
三,變量聲明
先猜一下下面的代碼會輸出什么結果?
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); } 結果是打印出10個10!解釋如下:setTimeout在若干毫秒后執行一個函數并且是在for循環結束后。for循環結束后,i的值為10.所以當函數被調用的時候,它會打印出10! 如果想打印出1~9,有兩個方法,1,將var變成let關鍵詞(ES6才開始支持)或者 2,使用立即執行函數捕捉每次循環的i的值。代碼如下: for (var i = 0; i < 10; i++) { (function(j){ setTimeout(function () { console.log(j); }, j * 10); })(i); }
1,關于變量作用域的一些說明:在ES6之前只有全局作用域和函數作用域,ES6新增了塊級作用域。 每次JavaScript引擎進入到一個作用域后(對于var聲明的變量,這個作用域是函數作用域;對于let聲明的變量,這個作用域是塊級作用域----if塊,for循環塊等等),它都會創建一個 變量 的環境。就算作用域內代碼已經執行完畢,這個環境與引擎 捕獲的變量依然存在。JavaScript引擎的資源回收機制 會在合適的時機自己回收 這個環境和 變量。當let聲明出現在循環體中時(即有了塊級作用域的概念),不僅是在循環里引入了一個新的變量環境,而且針對每次迭代都會創建這樣一個新作用域。這就是我們使用立即執行函數表達式做的事情。
2,const聲明 是聲明變量的另一種方式。const聲明與let聲明有相同的作用域規則(塊級作用域),和let的區別是 const聲明的變量在賦值后不能再被改變。注意一點:如果用const聲明一個引用類型,比如對象 const o = {name:"xx", age:12}, 聲明后可以改變變量o的內部狀態,如設置 o.name = "yyy"是被允許的。
3, 使用var可以重復聲明一個變量,最終你只會得到一個。 但是使用let聲明變量 在一個作用域內只能聲明一個同名變量。見下面的例子:
例一: let a = 3; if (true) { let a = 4;//塊級作用域中聲明的變量只在 這個塊級 起作用。 } console.log(a);//結果輸出是3 例二: function f(condition, x) { if (condition) { let x = 100;//塊級作用域中聲明的變量只在此塊中有意義,此塊中的x覆蓋了外面作用域的x. return x; } return x; } console.log(f(true, 0));//輸出100 console.log(f(false, 0));//輸出0
另外,如果塊級作用域里用到了一個變量,但是在塊里沒有此變量的聲明,則引擎會繼續向上找此塊所在的函數(或更高一級的塊等),如果還沒有引擎繼續向上找,如果一直找不到就會報錯
4,解構賦值
- 數組解構:數組中變量和右邊數組盡可能一一對應。
let [first, second] = [3,4];//first=3,second=4; [second, first] = [first, second];//first=4,second=3; let [first] = [1,2,3,4];//first = 1; let [,first, second] = [1,2,3,4];//first=2,second=3; let [,first,,second] = [1,2,3,4];//first=2,second=4; let [first,,second] = [[21,22,23],3]//first=[21,22,23],second=undefined let [first];//報錯 let first;//first=undefined let [first,second]=[3];//first=3,second=undefined //用 ... 語法創建剩余變量。 let [first, ...rest] = [1,2,3,4,6];//first=1,rest=[2,3,4,6];
[first, second] = [11, 22];//注意first,second沒有聲明過,first=11,second=22;
- 對象解構賦值
let o = { c: "foo", b: 12, a: "bar" };
let {a, b, d} = o;//a=bar, b=12, d=undefined;
//用 ... 語法創建剩余變量
let { a, ...rest } = o;//a=bar,rest={ c: 'foo', b: 12 };
/*
* 就像數組解構,可以使用未聲明的賦值,注意要將整個賦值用()括起來,
* 因為JavaScript通常會將以 { 開頭的語句解析為一個塊。
*/
({ e, f } = { e: "contemplate", f: "思考, 沉思" });//e=contemlate,f="思考, 沉思"
5,展開Spread: 展開和解構是相反的操作。
let first = [1, 2]; let second = [3, 4]; let obj = { a: "aaa", b: "bbb" }; let spread = [...first, ...second, { ...obj, a: "ambiance" }]; let spread2 = [...first, ...second, { a: "ambiance", ...obj }] //注意,展開對象時,同名的key,后面的將覆蓋前面的。 console.log(spread);//[ 1, 2, 3, 4, { a: 'ambiance', b: 'bbb' } ] console.log(spread2);//[ 1, 2, 3, 4, { a: 'aaa', b: 'bbb' } ] //展開是淺拷貝(shallow copy),不會改變原變量 console.log(first);//[1,2]; //展開對象時,它只能展開對象 自身的可枚舉屬性,不可枚舉屬性以及方法就不能展開了 class C { p = 12; m() { } } let c = new C(); let clone = { ...c }; clone.p; // ok clone.m(); // error!
四,接口
0,如果一個變量定義為某個接口定義的類型,那么
- 此變量的值必須含有接口中定義的所有屬性和方法,不能少!當然不包含可選屬性。
- 如果賦給變量的值是字面量,由于字面量在TypeScript中會受到額外的類型檢查,此字面量里含有的屬性或方法不能少,同時也不能多,即不能含有接口中不包含的屬性或方法。
- 如果賦給變量的值是另一個變量,則另一個變量中可以含有接口中不存在的方法或屬性。即不可以少,但是能多。
- 如果接口里面什么都沒有,會有特殊規則。沒什么實際價值。
1,可選屬性
interface SquareConfig { color?: string; width?: number; }
2,只讀屬性
※,TypeScript 具有ReadOnlyArray<T>,它與Array<T>相似,只是把所有可變方法去掉了,因此可以確保數組創建后再也不能被修改:
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
上面代碼的最后一行,可以看到就算把整個ReadonlyArray賦值到一個普通數組也是不可以的。 但是你可以用類型斷言重寫:
a = ro as number[];
※,readonly vs const
最簡單判斷該用readonly還是const的方法是看要把它做為變量使用還是做為一個屬性。 做為變量使用的話用 const,若做為屬性則使用readonly。
3,額外的屬性檢查
TypeScript中的對象字面量會被特殊對待:當將它們賦值給變量或作為參數傳遞的時候,它們會經過 額外屬性檢查。如果一個對象字面量存在任何“目標類型”不包含的屬性時,你會得到一個錯誤。
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { // ... } //注意,colour不是color,這里會得到一個錯誤:SquareConfig接口里不存在colour屬性。 let mySquare = createSquare({ colour: "red", width: 100 });
繞開這些額外的屬性檢查有以下幾種方法:
※,使用類型斷言
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
※,添加一個字符串索引簽名(后面會講)
interface SquareConfig { color?: string; width?: number; [propName: string]: any; } 這里索引簽名表示的是SquareConfig可以有任意數量的屬性,并且只要它們不是color和width,那么就無所謂它們的類型是什么。
※,將對象字面量賦值給一個變量,因為變量不會像對象字面量一樣會經過額外的屬性檢查。
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
※,
4,函數類型
※,接口能夠描述JavaScript中對象擁有的各種各樣的外形。 除了描述帶有屬性的普通對象外,接口也可以描述函數類型。為了使用接口表示函數類型,我們需要給接口定義一個調用簽名。 它就像是一個只有參數列表和返回值類型的函數定義。參數列表里的每個參數都需要名字和類型。
//函數類型接口定義了一個函數的各參數類型以及返回值類型 interface SearchFunc { (source: string, substring: string): boolean; } let mySearch: SearchFunc; //函數參數名稱可以和接口中不一致 mySearch = function (src: string, sub: string): boolean { return src.search(sub) > -1; } //也可以不指定函數參數和返回值的類型,TypeScript的類型系統會自己推斷出參數和返回值類型 mySearch = function (src, sub) { return src.search(sub) > -1; }
※,
5,可索引的類型(索引簽名)
※,和描述函數類型差不多,TypeScript還可以用接口描述那些 “通過索引得到” 的類型,比如a[10], ageMap['Evan']等。接口中定義了一個索引簽名,描述了索引的類型(如10,"Evan"的類型)以及索引的返回值類型(如a[10],ageMap["Evan"]的類型)。
//定義一個StringArray接口,它具有一個索引簽名(用中括號括起來的這個形式的就是索引簽名)。這個索引簽名表示當使用一個number類型索引StringArray時會得到string類型的返回值。 interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0];
※,TypeScript支持兩種索引簽名:字符串索引和數字索引。 可以同時使用兩種類型的索引,但是數字索引的返回值必須是字符串索引返回值類型的子類型。 這是因為當使用 number來索引時,JavaScript會將它轉換成string然后再去索引對象。 也就是說用 100(一個number)去索引等同于使用"100"(一個string)去索引,因此兩者需要保持一致。
class Animal { name: string; } class Dog extends Animal { breed: string; } // 錯誤:使用數值型的字符串索引,有時會得到完全不同的Animal! interface NotOkay { [x: number]: Animal; [x: string]: Dog; } let h:NotOkay = {}; h[100] = new Animal(); h["100"] = new Dog(); console.log(h[100]);//本想得到Animal{},輸出的卻是Dog{}
※,一個例子:
interface NumberDictionary { //這個是類型的索引簽名,索引是string類型,索引返回值是number類型。 [index: string]: number; length: number; // 可以,length是索引string類型,其索引后的返回值是number類型 name: string // 錯誤,`name`的類型與索引類型返回值的類型不匹配 }
※, 將索引簽名設置為只讀,這樣就防止了給索引賦值:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
你不能設置myArray[2],因為索引簽名是只讀的
6,類類型
※,類可以用關鍵字implements實現一個接口,實現接口時,接口里的屬性或方法類中必須有,也可以定義接口中不存在的屬性或方法。即可以多不可以少。
※,類靜態部分與實例部分的區別(有些難理解)
當你操作類和接口的時候,你要知道類是具有兩個類型的:靜態部分的類型和實例的類型。 你會注意到,當你用構造器簽名去定義一個接口并試圖定義一個類去實現這個接口時會得到一個錯誤:
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
這里因為當一個類實現了一個接口時,只對其實例部分進行類型檢查。 constructor存在于類的靜態部分,所以不在檢查的范圍內。
因此,我們應該直接操作類的靜態部分。 看下面的例子,我們定義了兩個接口, ClockConstructor為構造函數所用和ClockInterface為實例方法所用。 為了方便我們定義一個構造函數 createClock,它用傳入的類型創建實例。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
因為createClock的第一個參數是ClockConstructor類型,在createClock(AnalogClock, 7, 32)里,會檢查AnalogClock是否符合構造函數簽名。
7,繼承接口
※,一個接口可以繼承多個接口。
8,混合類型 (有點繞)
※,
9,接口繼承類
※,當接口繼承了一個類類型時,它會繼承類的成員(即屬性和方法)但不包括其實現。 就好像接口聲明了所有類中存在的成員,但并沒有提供具體實現一樣。 接口同樣會繼承到類的private和protected成員。 這意味著當你創建了一個接口繼承了一個擁有私有或受保護的成員的類時,這個接口類型只能被這個類或其子類所實現(implement)。
五,類
1,繼承
※,和Java一樣,TypeScript不允許多繼承。一個類只能繼承一個類。
※, 父類也叫基類,超類。子類也叫派生類。
※,子類繼承了父類,如果子類中沒有構造函數則會自動執行父類的構造函數。如果子類 中有構造函數,則它 必須 調用 super(parameters),它會執行基類的構造函數。 而且,在構造函數里訪問 this的屬性之前,我們 一定 要調用 super(parameters)。 這個是TypeScript強制執行的一條重要規則。
※,訪問修飾符(public ,protected, private):public 修飾符可以省略。protected修飾的屬性和方法可以在聲明其的類內部 以及 繼承了這個類的子類內部用this調用。實例化后不能調用此屬性或方法。 private修飾的屬性或方法只能在聲明其的類內部用this調用。如果一個類的構造函數被標記為protected,那么這個類不能被實例化,但是可以被繼承。如果一個類的構造函數被標記為private,那么這個類不能被實例化,也不能被繼承。
TypeScript使用的是結構性類型系統。 當我們比較兩種不同的類型時,并不在乎它們從何處而來,如果所有成員的類型都是兼容的,我們就認為它們的類型是兼容的。
然而,當我們比較帶有 private或 protected成員的類型的時候,情況就不同了。 如果其中一個類型里包含一個private成員,那么只有當另外一個類型中也存在這樣一個 private成員, 并且它們都是來自同一處聲明時,我們才認為這兩個類型是兼容的。 對于 protected成員也使用這個規則。
下面來看一個例子,更好地說明了這一點:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // 錯誤: Animal 與 Employee 不兼容.
※,可以使用readonly修飾符修飾類的屬性,將其標記為只讀屬性。readonly不能修飾類的方法。
※,
2,參數屬性
※,如果一個類的構造函數的參數有public, protected,private修飾符,那么就相當于類定義了一個同名屬性,同時構造函數里給其賦了值。只有readonly修飾參數也可以,相當于public readonly xxx.
class Person { constructor(public name: string) { } } //以上類等同于如下 class Person { public name: string; constructor(name: string) { this.name = name; } }
※,
3,存取器(get和set方法)
※,類中的屬性可以通過getters 和 setters 來控制對此屬性的訪問。比如,設置一個屬性的值時,如果需要滿足一定的條件才能設置,那么此時可以通過getters/setters。只帶有get方法而不帶有set方法的屬性被自動推斷為readonly屬性。存取器的例子如下:
let passcode = "secret passcode"; class Employee { private_fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
※,
4,靜態屬性
※,TypeScript的靜態屬性或方法只能通過類名訪問。沒有self之類的東西
※,
5,抽象類
※,抽象類作為其它派生類的基類使用。抽象類不能被實例化。不同于接口,抽象類可以包含成員的實現細節。
※,抽象類中可以定義抽象方法(抽象方法只能出現在抽象類中,普通類中不能定義抽象方法),抽象方法和接口語法差不多,只有方法簽名,沒有實現。但是和接口不一樣的是,抽象方法必須有關鍵字abstract,實際相當于public abstract,也可以是protected abstract,但是不能是private abstract,因為抽象方法必須被派生類實現(但是抽象類中的非抽象方法不需要一定被派生類實現)。
※,
6,
7,
8,
9,
六,函數
在TypeScript里,雖然已經支持類,命名空間和模塊,但函數仍然是主要的定義 行為的地方。 TypeScript為JavaScript函數添加了額外的功能,讓我們可以更容易地使用。
1,函數的類型定義:
※,返回值類型:TypeScript能夠根據返回語句自動推斷出返回值類型,因此我們通常省略它。
※,函數類型:如果定義一個變量為函數類型,其完整的寫法是:
let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; }; 還有一種寫法,用帶有調用簽名的對象字面量來說明函數類型, //除了使用 (x:string)=>string,還可以如下: let sayHello: { (x: string): string } = function (theName: string): string { console.log("hello " + theName); return "hello " + theName; }; console.log(sayHello); sayHello("Evan")
※,類型推斷:如果你在賦值語句的一邊指定了類型但是另一邊沒有類型的話,TypeScript編譯器會自動識別出類型:
// TypeScript根據右邊值得類型推斷變量myAdd的完整類型。 let myAdd = function(x: number, y: number): number { return x + y; }; // TypeScript根據myAdd的類型推斷變量x,y的類型為number,其返回值也是number let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; }; //甚至還可以這么寫(不推薦)。TypeScript會根據myAdd的類型推斷出函數有兩個number類型的參數,調用myAdd時如果參數個數或類型不對會報錯,但是這個樣子聲明這個函數時是不會報錯的。 let myAdd: (baseValue: number, increment: number) => number = function () {return 33;}
※,
2,可選參數和默認參數 (ps,Java中不支持默認參數,Java解決此問題的方法是方法重載!)
※,TypeScript的函數,調用時傳入的參數個數和聲明時的參數個數要保持一致(JavaScript中沒有硬性要求)。
※,可選參數:在函數聲明時,參數后面加個 “?”可表明此參數是可選的。注意,可選參數 必須放在 必選參數 后面。
※,默認參數:在函數聲明時,參數可以初始化一個默認值,當調用時沒有傳入參數或傳了一個undefined(注意,傳null時此參數值為null而不使用默認值)時,此參數默認使用默認值。默認參數不必在必須參數后面。如果帶默認值的參數出現在必須參數之前,則調用時必須明確的傳入undefined值來獲取默認值。
※,可選參數 和 放在必須參數后面的默認參數 共享參數類型:(注意是有條件的,如果默認參數是在必須參數的前面,此時默認參數是必須要傳的,則和可選參數不再共享參數類型)
function buildName(firstName: string, lastName?: string) {
// ...
}
和
function buildName(firstName: string, lastName = "Smith") {
// ...
}
共享同樣的類型(firstName: string, lastName?: string) => string。 默認參數的默認值消失了,只保留了它是一個可選參數的信息。
※,
3,剩余參數
※,JavaScript里,可以使用 arguments 來訪問所有傳入的參數。在TypeScript里,可以使用剩余參數將所有參數收集到一個變量里。剩余參數會被當做個數不限的可選參數。 可以一個都沒有,同樣也可以有任意個。 編譯器創建參數數組,名字是你在省略號( ...)后面給定的名字,你可以在函數體內使用這個數組。
這個省略號也會在帶有剩余參數的函數類型定義上使用到:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
※,
4,this (函數中的this)
※,JavaScript里,this的值是在函數被調用的時候才確定的。這是個既強大又靈活的特點,但是需要程序員弄清楚函數調用的上下文,而這并不是一件簡單的事情。看一個例子:
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { return function() { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); console.log("card: " + pickedCard.card + " of " + pickedCard.suit); ※,運行后發現會報錯!調用cardPicker()的時候報錯了,因為這里只是獨立的調用了cardPicker(),頂級的非方法式調用會將this視為window對象(注意:嚴格模式下,this為undefined而不是window對象), ※,JavaScript(ECMAScript 6或叫ECMAScript 2015之前)解決這個問題的方法有兩個:bind() 和 call()/apply(),使用如下: 1,call() let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker.call(deck);//指定函數cardPicker在deck的作用域上調用。 2,bind() let cardPicker = deck.createCardPicker(); let newCardPicker= cardPicker.bind(deck);//bind()返回一個改變了作用域的函數。 let pickedCard = newCardPicker(); 以上兩種方式都可以得到預想中的結果。
※,this 和 箭頭函數(ECMAScript 6中的語法):
基礎:各種特殊形式的箭頭函數,比如a=>a, ()=>a, a=>({foo:"bar"})//單語句返回一個對象時,不能直接用{},要用()括起來。
進階[非常非常非常好的解析了箭頭函數中的this的一般性規則]
【自己總結】箭頭函數并不綁定 this,arguments,super(ES6),抑或 new.target(ES6)。在箭頭函數上使用call()或者apply()調用箭頭函數時,無法對this進行綁定,即傳入的第一個參數被忽略。箭頭函數中的this的值 確切來說就是整個箭頭函數所在位置 處的那個地方的this值。
//例一 function foo() { setTimeout(() => { console.log(this); console.log("id:", this.id); }, 100); } /** * 解析:整個箭頭函數在setTimeout()的參數里,這里的this就是foo函數作用域的this,也就是誰調foo函數,this就是誰 */ foo();//this是window對象,this.id:undefined foo.call({id:33});//this是傳入的對象{id:33},this.id:33.注意這里是對foo應用call()而不是對箭頭函數應用call() //例二: let test = () => { console.log(this); console.log(this.age); } /** * 解析:整個箭頭函數就在全局的作用域里,箭頭函數中的this就是全局作用域的this,即window對象 */ test.call({ age: 333 });//對箭頭函數應用call()無法綁定this,即傳入的對象會被忽略。this是window對象,this.age:undefined
在ECMAScript 以及 TypeScript中,可以使用箭頭函數 使函數被返回時就已經綁定好正確的this值。箭頭函數能保存函數創建時的this值而不是調用時的值。【箭頭函數的this始終指向函數定義時的this,而非執行時】。
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here。下面的箭頭函數可以在這里就捕獲到this的值,而不是調用時才確定this的值。 return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); alert("card: " + pickedCard.card + " of " + pickedCard.suit);
※,給函數加上虛假的this參數:
可以給函數加上一個this參數,這個參數是虛假的,必須放在所有參數的最前面。這個參數不會影響其他的參數,也不算做參數的個數。這么做的作用是可以指出this的類型,明確指出這個函數只能在this對應的對象上調用。
interface Person { //第一個this不算參數,這個方法需要一個參數 getName(this:Person, theName:string):string; } let p1: Person = { //這里第一個參數也可以寫為 this:Person,并且推薦這么寫,因為可以明確指出這個函數只能在Person對象上調用。 getName: function (theName:string) { console.log(this); return theName; } } //調用時只需要一個參數即可。 p1.getName("Evan Tong");
※,TypeScript中的函數重載。
JavaScript中函數的簽名(名字,參數個數,參數類型等)很靈活也很隨意,但是在TypeScript這種類型系統的語言中,這種做法就不太好了,比如調用時無法知曉具體的參數個數即類型。TypeScript支持函數重載。方法如下:
let suits = ["hearts", "spades", "clubs", "diamonds"]; /** * 重載列表位置必須放在具體的實現的函數之前。之間不允許寫任何其他代碼。 * 這里重載列表是是前2個,第三個不是重載列表的一部分。 * 重載后pickCard函數在調用的時候會進行正確的類型檢查,參數是一個對象或一個數字,以其他參數調用pickCard都會產生錯誤 * 為了讓編譯器能夠選擇正確的檢查類型,它與JavaScript里的處理流程相似。 它查找重載列表,嘗試使用第一個重載定義。 * 如果匹配的話就使用這個。 因此,在定義重載的時候,一定要把最精確的定義放在最前面。 */ function pickCard(x: { suit: string; card: number; }[]): number; function pickCard(x: number): { suit: string; card: number; }; function pickCard(x): any { // Check to see if we're working with an object/array // if so, they gave us the deck and we'll pick the card if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Otherwise just let them pick the card else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)]; console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
※,
七,泛型 (Generics)
1,泛型之Hello World
/* * T是類型變量,它是一種特殊的變量,只用于表示類型而不是值。可以理解為函數接受兩種參數:類型參數T(用中括號括起來) 和 參數arg。這個函數叫做泛型函數 */ function identity<T>(arg: T): T { return arg; }
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以這樣理解loggingIdentity的類型:泛型函數loggingIdentity,接收類型參數T和參數arg,它是個元素類型是T的數組,并返回元素類型是T的數組。 如果我們傳入數字數組,將返回一個數字數組,因為此時 T的的類型為number。 這可以讓我們把泛型變量T當做類型的一部分使用,而不是整個類型,增加了靈活性。
我們也可以這樣實現上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
2,泛型函數的類型
泛型函數的類型與非泛型函數的類型沒什么不同,只是有一個類型參數在最前面,像函數聲明一樣:
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;
我們還可以使用帶有調用簽名的對象字面量來定義泛型函數:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
這引導我們去寫第一個泛型接口了。 我們把上面例子里的對象字面量拿出來做為一個接口:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
一個相似的例子,我們可能想把泛型參數當作整個接口的一個參數。 這樣我們就能清楚的知道使用的具體是哪個泛型類型(比如: Dictionary<string>而不只是Dictionary)。 這樣接口里的其它成員也能知道這個參數的類型了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
注意,我們的示例做了少許改動。 不再描述泛型函數,而是把非泛型函數簽名作為泛型類型一部分。 當我們使用GenericIdentityFn的時候,還得傳入一個類型參數來指定泛型類型(這里是:number),鎖定了之后代碼里使用的類型。 對于描述哪部分類型屬于泛型部分來說,理解何時把參數放在調用簽名里和何時放在接口上是很有幫助的。
除了泛型接口,我們還可以創建泛型類。 注意,無法創建泛型枚舉和泛型命名空間。
※,
3,泛型類
4,泛型約束
※,
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; }
※,你可以聲明一個類型參數,且它被另一個類型參數所約束。 比如,現在我們想要用屬性名從對象里獲取這個屬性。 并且我們想要確保這個屬性存在于對象 obj上,因此我們需要在這兩個類型之間使用約束。
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // okay getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
※,
5,在泛型里使用類類型
6,
八,枚舉(Enums)
1,TypeScript支持兩種枚舉:數字的和基于字符串的枚舉。
※,字符串枚舉必須給每個枚舉成員一個初始化的值。
※,每個枚舉成員都有一個值,值可以分為兩種類型:計算出來的 和 常量的。
※,數字枚舉 有反向映射機制(即根據枚舉屬性名可以得到枚舉成員的值,也可以反過來,根據枚舉成員的值得到枚舉成員的名稱。),而字符串枚舉沒有反向映射。
※,
2,常量枚舉(const enums)
※,常量枚舉通過在枚舉上使用 const修飾符來定義。
※,常量枚舉成員的值只能是常量的,不能是計算出來的。不同于常規的枚舉,它們在編譯階段會被刪除。 常量枚舉成員在使用的地方會被內聯進來。 之所以可以這么做是因為,常量枚舉不允許包含計算成員。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
生成后的代碼為:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
※,
3,
九,類型兼容性
1,介紹
TypeScript里的類型兼容性是基于結構子類型的。 結構類型是一種只使用其成員來描述類型的方式。 它正好與名義(nominal)類型形成對比。(譯者注:在基于名義類型的類型系統中,數據類型的兼容性或等價性是通過明確的聲明和/或類型的名稱來決定的。這與結構性類型系統不同,它是基于類型的組成結構,且不要求明確地聲明。) 看下面的例子:
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();
在使用基于名義類型的語言,比如C#或Java中,這段代碼會報錯,因為Person類沒有明確說明其實現了Named接口。
TypeScript的結構性子類型是根據JavaScript代碼的典型寫法來設計的。 因為JavaScript里廣泛地使用匿名對象,例如函數表達式和對象字面量,所以使用結構類型系統來描述這些類型比使用名義類型系統更好。
2,比較兩個函數的兼容
3,枚舉的兼容性
※,枚舉類型與數字類型兼容,并且數字類型與枚舉類型兼容。不同枚舉類型之間是不兼容的。比如,
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green; // Error
※,
4,類的兼容性
※,類與對象字面量和接口差不多,但有一點不同:類有靜態部分和實例部分的類型。 比較兩個類類型的對象時,只有實例的成員會被比較。 靜態成員和構造函數(屬于類的靜態部分)不在比較的范圍內。
class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
a = s; // OK
s = a; // OK
※,類的私有成員和受保護成員
類的私有成員和受保護成員會影響兼容性。 當檢查類實例的兼容時,如果目標類型包含一個私有成員,那么源類型必須包含來自同一個類的這個私有成員。 同樣地,這條規則也適用于包含受保護成員實例的類型檢查。 這允許子類賦值給父類,但是不能賦值給其它有同樣類型的類。
※,
5,泛型的兼容性
6,在TypeScript里,有兩種兼容性:子類型和賦值。 它們的不同點在于,賦值擴展了子類型兼容性,增加了一些規則,允許和any來回賦值,以及enum和對應數字值之間的來回賦值。
語言里的不同地方分別使用了它們之中的機制。 實際上,類型兼容性是由賦值兼容性來控制的,即使在implements和extends語句也不例外。
更多信息,請參閱TypeScript語言規范.
7,
十,高級類型 (對比基礎類型:string,number,數組,null,object等)
1,交叉類型(intersection types)
※,交叉類型是將多個類型合并為一個類型。 這讓我們可以把現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。 例如, Person & Serializable & Loggable同時是 Person 和 Serializable 和Loggable。 就是說這個類型的對象同時擁有了這三種類型的成員。
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)) { (<any>result)[id] = (<any>second)[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
※,
※,
2,聯合類型 (union types)
※,
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true); // errors during compilation
聯合類型表示一個值可以是幾種類型之一。 我們用豎線( |)分隔每個類型,所以 number | string | boolean表示一個值可以是 number, string,或 boolean。
如果一個值是聯合類型,我們只能訪問此聯合類型的所有類型里共有的成員。
※,
3,
5,
十一,
1,
十二,
1,
《二》, 隨記(問題及相關記錄)
※,關于JavaScript的變量提升和函數提升
一,變量提升 console.log(a);//報錯,a未定義 ------------------------------- console.log(a);//undefined var a=3 解析:實際執行順序如下 var a;//變量提升,全局作用域范圍內,此時只是聲明,并沒有賦值 console.log(a);//undefined a = 3;//此時賦值 --------------------------------- 二,函數提升 函數有兩種,聲明式和字面量式(匿名函數),只有聲明式會提升而且優先級高于變量提升 console.log(f1);//function f1(){} var f1 = "abc"; function f1(){} console.log(f1);//abc
f1();//報錯,f1不是函數
解析:實際執行順序如下 function f1(){}//函數提升,整個代碼塊提升到文件最開始(和變量提升不同) console.log(f1);//function f1(){} var f1 = "abc"; console.log(f1);//abc
f1();//報錯,此時f1="abc",不是函數,不能被調用。
※,JS函數的靜態屬性和實例屬性
let A = function () { this.b= "bbbb"//b是函數A的實例屬性 return ("xxx"); } A.c = "ccc";//c是函數A的靜態屬性 console.log(A.b);//undefined 實例屬性不能用函數本身調用 console.log(A.c);//ccc 靜態屬性直接用函數本身調用 let a = new A(); l(a.b);//bbbb 實例屬性可以在實例上調用
※
浙公網安備 33010602011771號