《零》,概述javascript
※,vscode 的智能提示用的是 TypeScript language service,這個服務有個叫 AutoAutomatic Type Acquisition(ATA)的東西,ATA會根據package.json中列出的npm modules拉取這些模塊的類型聲明文件(npm Type Declaration files,即 *.d.ts 文件),從而給出智能提示。html
※,安裝TypeScript 編譯器: npm install -g typescript(全局安裝) 或 npm install --save-dev typesript (僅將ts編譯器安裝在當前項目中)。若是想裝特定版本的Ts可使用npm install -g typescript@3.3.1java
※,tsc -h 查看幫助文檔。各類編譯選項參數和其含義。git
※,tsc --init 能夠生成一個tsconfig.json配置文件(固然也能夠手動建立此文件)。此文件配置了TypeScript項目信息,如編譯選項, 包含的文件等等。沒有此文件則使用默認設置。使用tsc命令編譯ts文件時,ts編譯器會自動查找 tsconfig.json,並按照其中的配置編譯ts文件。程序員
※,tsc xxx.ts用於編譯某個ts文件,也能夠直接運行tsc命令,編譯全部的ts文件。若是有tsconfig.json文件,直接運行tsc將會編譯tsconfig.json中包含的全部ts文件。github
※,tsconfig.json中的一些配置項說明:typescript
{
"compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "out",//此屬性配置了編譯後js文件存儲的位置
"sourceMap":true, //true表示編譯後同時生成map文件,指示ts和js文件的對應關係。
},
"files":{
},
"extends":{
},
.....
}※,npm
《一》,文檔學習 https://www.tslang.cn/docs/home.html json
一,快速入門數組
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,若是一個變量定義爲某個接口定義的類型,那麼
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 實例屬性能夠在實例上調用
※