簡介:typescript是C#之父主導的一門語言,本質上是向Javascript語言添加了可選的靜態類型和基於面向對象的諸多特性。相當於javascript的超集,其包含es6。由於是和C#之父創造的,所以這里我采用和C#對比的方式學習他們之間的不同點,和主流面向對象語言(C#,Java)中相符的性質將不作記錄。
1. 數字類型:C#有一系列限定大小范圍的int,short,int16,long等整形,還有float,double等小數類型,而ts(TypeScript簡稱,為方便,以下皆簡稱ts)只有一種數字類型代表C#上面提到的全部數字類型:number,
let a:number=0b1010; //二進制
let b:number=0o744; //八進制
let c:number=0xf00d;//十六進制
2. 字符串類型:ts是string,和C#一致,不過ts增加了一種獨特的語法,有點類似於C#的StringBuild對象,可以在字符串中嵌入占位符。使用字符串反引號(`)來定義多行文本,和內嵌表達式${expr}
let words:string=`hello,${name}你好,歡迎你來到${address}`;
3. 元組類型:元組類型用來表示已知元素數量和類型的數組,各元素的類型不必相同。這個是ts獨有的,感覺該類型不會在實際編程中有很大用處。
4. 任意值類型:是ts針對編程時類型不明確變量使用的一種數據類型,常用於一下三種情況:
4.1 變量的值會動態變化時,比如來之第三方代碼庫,任意值類型可以讓這些變量跳過編譯階段的類型檢查。
4.2 改寫現有代碼時,任意值允許在編譯時可選擇的包含或移除類型檢查。
4.3 定義各種類型數據的數組時。
5. never類型:是其他類型(包括null和undefined)的子類型,代表從不會出現的值,理解這句話很重要,never類型的變量只能被never類型所賦值,在函數中它通常表現為拋出異常,或無法執行到終止點。
如:let y:never=(()=>{throw new Error('')})();
6. let申明:let是用於聲明變量的,和javascript中的var區別是let只能在塊級作用域內有效,一般常在函數的作用域定義。並且使用了let定義的變量就具有了C#中變量定義的諸多限制,如不能重復,不能聲明之前使用。
6.1 ts使用let聲明變量類型有一點需要注意:如果一個類型可能出現null或者undefined,可以用|來支持多種類型。
如:let x:number | undefined;
let y:string | null | undefined;
7. 結構:是Es6的一個重要特性,其作用就是將聲明的一組變量與相同結構的數組或者對象的元素值一一對應,並將變量相對應元素進行賦值。
7.1 數組結構:
如:let input=[1,2];
let [first,second]=input;
可以得出:first==0,second==2;
也可作用與已聲明的變量:[first,second]=[second,first]; //變量交換
7.2 對象結構:
如: let test={x:0,y:10,width:15,height:16};
let {x,y,width,height}=test;
8. 數參數定義:javascript里,被調函數的每個參數都是可選的,而在ts里面被調函數的每個參數都是必傳,在編譯時會檢查每個函數是否傳值。
8.1 可選參數:在參數名旁邊加上?號可以是參數變成可選參數,可選參數必須位於必選參數的后面的位置:
如:function max(x:number,y?:number)
調用: max(2);,max(2,4); //正確方式
max(2,4,7); //錯誤方式
8.2 默認參數:默認參數就是在定義函數參數時直接給該參數賦予一個默認值,這樣可以在調用函數時如果不傳改默認參數,該參數就是默認值。默認參數不必放在必選參數位置之后。
如: function max(x:number,y=4)
調用:max(2),max(2,4),max(2,undefined) //正確 如果默認參數定義到了必選參數前面,用戶必須顯示的傳入undefined。
max(2,4,7) //錯誤
8.3 剩余參數: 當同時需要操作多個參數,或者並不知道會有多少參數傳遞進來時,就可以使用剩余參數。剩余參數定義方式采用"..."號做外參數名前綴,參數類型(形參)必須使用數組類型,且只能是函數的最后一個參數,
這樣調用函數時,可以傳入多個實數,在函數體中可以通過形參數組取得所有傳入的剩余參數。
9. 箭頭函數:ts提供的箭頭函數(=>),可以在函數創建時就綁定this,從而解決javascript中的this由於在代碼中的不同調用方式而導致的this指代window對象或者undefined。
如:let gif={
gifts:["xzm","panmin","laop","choulp"],
giftPicker:function(){
return this.gifts[1]
}
}
調用:let pickGift=gif.giftPicker();
pickGift(); //報錯,Cannot read property '5' of undefined(...)
使用箭頭函數:
let gif={
gifts:["xzm","panmin","laop","choulp"],
giftPicker:function(){
return ()=> {this.gifts[1]
}
}
}
10. 類的構造函數:使用construction來作為構造函數名定義,派生類構造函數必須調用super(),他會調用基類的構造方法。
11. 參數屬性:這里其實叫參數訪問限定符更合適,有點類似C#的屬性名前面的限定符(public,private,protected),只不過ts給他更多的意義,參數屬性是通過給構造函數參數添加一個訪問限定符,
它可以方便的在一個地方定義並初始化類成員,參數屬性是一種語法糖,類似於:
class car{
public wheel:number;
construction(wheel:number){this.wheel=wheel; }
}
使用參數屬性:
class car {
construction(public wheel:number){
this.wheel=wheel;
}
}
減少了一些原本啰嗦的代碼量。
12. 模塊概念:Es6引入了模塊的概念,感覺他有點像C#的名稱空間,Java的包,但是又有些區別,這些區別是javascript的語法原因。
12.1 首先,模塊是自聲明的,兩個模塊之間的關系是通過在文件級別上使用import和export來建立。任何包含頂級import或者export的文件都會被當成一個模塊。
12.2 其次,模塊在其自身的作用域里執行,而不是在全局作用域里,定義在一個模塊里的變量,函數和類等,在模塊外部是不可見的,除非明確的使用export到處它們,
類似的,如果想使用其他的模塊導出的變量,函數,類和接口時,必須先通過import導入它們。
12.3 模塊使用模塊加載器去導入他的依賴,模塊加載器在代碼運行時會查找並加載模塊間的所有依賴。在Angular中,常用的模塊加載器有SystemJS和webpack.
13. 模塊導出方式,分為以下三種:
13.1 導出聲明:任何模塊都能通過export關鍵字導出。
如:export const x=1;
exprot interface Indentity{}
exprot class Car{}
13.2 導出語句:當需要對導出的模塊進行重命名時,可以使用導出語句。
如:
class car{}
export { car };
export { car as BigCar };
13.3 模塊包裝:當需要修改和擴展已有的模塊,並導出供其他模塊調用。
//導出原先的驗證器,但做了重命名
export { ErpIdentityValidate as RegExpBaseZipCodeValidator } from "./ErpIndentityValidate";
//一個模塊可以包裹多個模塊,並把新的內容以一個新的模塊導出
export * from "./IndentityValidate";
export * from "./ErpIdentityValidate";
14. 模塊導入方式:模塊導入與模塊導出相對應,可以使用import關鍵字來導入當前模塊依賴的外部模塊。有如下幾種方式:
14.1 導入一個模塊: import { EnpIndentityValidate } from "./ErpIdentityValidate";
14.2 別名導入: import { EnpIndentityValidate as eiv } from "./ErpIdentityValidate";
14.3 對整個模塊進行別名導入: import * as validate from "./ErpIdentityValidate";
15. 模塊的默認導出:模塊可以用default關鍵字實現默認導出的功能,每個模塊可以有一個默認導出。
另外,類和函數聲明可以直接省略導出名來實現默認導出,默認導出有力於減少調用方調用模塊的層數,減少代碼的冗余。
15.1 默認導出類示例:
export default class ErpIdentityValidate{};
導入:
import validate from "./ErpIdentityValidate";
15.2 默認導出函數示例:
export default function{};
導入:
import validate from "./ErpIdentityValidate";
使用導出的函數:
validate();
15.3 默認導出值:
export default "TypeScript";
導入:
import name from "./ErpIdentityValidate";
16. 模塊設計原則:
16.1 盡可能的在頂層導出:頂層導出可以降低調用方使用的難度,過多的"."操作使得開發者要記住過多的細節,所以盡量使用默認導出(使用者可以直接導入對象)或者
頂層導出(頂層導出方便調用者一目了然模塊有哪些可供導入的對象),尤其是單個對象可以采用默認導出方式。
16.2 明確的列出導入的名字:在導入的時候盡可能明確的指定導入對象的名稱,這樣只要接口不變,調用方式就可以不變,從而降低了導入跟導出的耦合度,做到面向接口編程。
如:import { cat,dog } from "./ErpIdentityValidate";
16.3 使用命名空間導入:
如://MyLargemodule.ts
export class Dog{}
export class Cat{}
export class Tree{}
導入: import * as myLargemodule from "./MyLargemodule";
let x=new myLargemodule.Dog();
16.4 使用模塊包裝進行擴展:當需要擴展一個模塊的功能時,推薦的方案是不要去更改原來的對象,而是導入該對象,再繼承該對象,擴展導出一個新的對象。
如:
export class M{}; //ModuleA.ts
import { ModuleA } from "./ModuleA";
export class ModuleB extends ModuleA{}
17. 接口:TypeScript接口的使用方式類似Java,同時還增加了更靈活的接口類型,包括屬性,函數,可索引(indexable TypeScript)和類等類型。
17.1 屬性類型接口:接口中只定義了屬性的接口,實現該接口的方式只需要"形式上"的滿足接口的要求即可,
如: interface FullName{
firstName:string;
secondName:string;
}
function printLabel(name:FullName){}
let obj={age:10,firstName:'xzm',secondName:'panmin'}
printLabel(obj); //obj對象只需要包含一個firstName和secondName屬性,且類型都是string既可。
17.1.2 可選屬性,typescript提供了對可能存在的屬性進行預定義,並兼容不傳值的情況。其定義方式和普通接口沒什么大的差別,就是可選屬性變量名后添加一個?號。
如:interface FullName{
firstName:string;
secondName?:string;
}
function printLabel(name:FullName){}
let obj={firstName:"xzm"};
printLabel(obj);
17.2 函數類型接口:定義函數類型接口時,需要明確定義函數的參數列表和返回值類型,且參數列表的每個參數都要又參數名稱和類型,不需要定義函數名。
如:interface Cat{
(name:string,salt:string):string
}
let tomcat:Cat;
tomcat=function(name:string,salt:string){return "miaomiao"}
注意:函數的參數名,和類型必須保持一致,同時函數的返回類型也必須保持一致。
17.3 可索引類型接口:用來描述那些可以通過索引得到的類型,它包含一個索引簽名(類似數組的下標,字典的key),表示用來索引的類型與返回值類型,即通過特定的索引來得到指定類型的返回值。
這是一個在C#和Java中還沒有的特性。索引簽名支持字符串和數字兩種數據類型,使用這兩種類型的最終返回值是一樣,即當使用數字類型來索引時,javascript最終也會將其轉換成自字符串
類型后再去索引對象。
個人的感覺可索引類型接口,其實有點想指定格式類型的數組,或者字典(Map).
如:
inteface UserArray{
[index:number]:string; //索引類型數字類型,近似於數組
}
interface UserObject{
[index:string]:string; //索引類型是字符串類型,相當於字典
}
let userArray:UserArray;
let userObject:UserObject;
userArray=["zhangsan","lisi"];
userObject={"name1":"張三","name2":"李四"};
17.4 類類型接口:基本上和C#,Java里面的傳統接口定義並無什么大的差別。類繼承接口采用implements關鍵字。
17.5 接口擴展:接口也可以實現相互擴展,接口繼承采用extends關鍵字。
18. 裝飾器:(Decorators)是一種特殊類型的聲明,它可以被附加到類聲明,方法,屬性或參數上。裝飾器由@符號緊接一個函數名稱,如@expression,expression求值后必須是一個函數,
在函數執行的時候裝飾器的聲明方法會被執行。裝飾器是用來給附着的主體進行裝飾,添加額外行為的。
個人見解:感覺typescript的裝飾器就是Java的注解,C#的特性,算是一種元編程,定義在對象上,用於代碼執行前后,做額外的事情,主要提供面向截面編程。
TypeScript官方介紹:裝飾器(Decorators)為我們在類的聲明及成員上通過元編程語法添加標注提供了一種方式。 Javascript里的裝飾器目前處在
建議征集的第一階段,但在TypeScript里已做為一項實驗性特性予以支持。
注意:裝飾器是一項實驗性特性,在未來的版本中可能會發生改變。
若要啟用實驗性的裝飾器特性,你必須在命令行或tsconfig.json里啟用experimentalDecorators編譯器選項:
命令行:
tsc --target ES5 --experimentalDecorators
tsconfig.json文件啟用:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
18.1 裝飾器求值:類中不同聲明的裝飾器將按以下規定的順序應用:
1.參數裝飾器,然后依次是方法裝飾器,訪問符裝飾器,或屬性裝飾其應用到每個實例成員。
2.參數裝飾器,然后依次是方法裝飾器,訪問符裝飾器,或屬性裝飾其應用到每個靜態成員。
3.參數裝飾器應用到構造函數。
4.類裝飾器應用到類。
18.2 類裝飾器:在類聲明之前聲明,類裝飾器應用於類的構造函數,可以用來監視,修改或替換類定義。
重點1:類裝飾器表達式會在運行時當做函數被調用,類的構造函數被當做其唯一的參數。
重點2:如果類裝飾器返回一個值,它會使用提供的構造函數來替換類的聲明。(也就是說,可以用類裝飾器返回一個函數的形式來動態替換類的原本構造函數)。
注意:
如:
@sealed
class Greeter{
greeter:string;
constructor(message:string){
this.greeter=message;
}
greeter(){
return "hello"+this.greeter;
}
}
@sealed裝飾器定義如下
function sealed(constructor:Function){
Object.seal(constructor);
Object.seal(constructor.protected); //當@sealed被執行時,它將密封該了類的構造函數和原型。
}
18.3 方法裝飾器:聲明在一個方法的聲明之前,它會被用到方法的屬性描述符上(descriptor),可以用來監視,修改或替換方法定義。方法裝飾器不能用在聲明文件(.d.ts),
重載或者任何外部上下(比如declare的類中)中。
方法裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
1.target:對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
2.properrtyKey:方法(成員)的名稱。
3.descriptor:成員的屬性描述符。
注意:如果代碼的輸出版本小於ES5,屬性描述符將會是undefined。
如果方法裝飾器的返回一個值,它會被用作方法的屬性描述符,如果代碼的輸出目標小於ES5,返回值會被忽略。
其中descriptor類型為TypedPropertyDescriptor, 在typescript中定義如下:
interface TYpedPropertyDescriptor<T>{
enumerable?:boolean; //是否可遍歷
configurable?:boolean; //屬性描述是否可改變或者屬性是否可刪除
writable?:boolean; //是否可修改
value?:T; //屬性的值
get?:()=>T; //屬性的訪問器函數(getter)
set?:(value:T)=>void //屬性的設置器函數
}
如:
class TestClass{
@log
testMethod(arg:string){
return "xzm:"+arg;
}
}
裝飾器@log的實現:
function log(target:Object,properrtyKey:string,descriptor:TypedPropertyDescriptor<any>){
let origin=descriptor.value; //通過方法屬性描述符的value屬性,取得有關方法對象
descriptor.value=function(...args:any[]){
console.log("args:"+JSON.stringify(args)); //調用前
let result=origin.apply(this,args); //調用方法
console.log("The result" + result);
return result;
}
return descriptor;
}
使用代碼測試: new TestClass().testMethod("test method descorator");
結果輸出如下: agrs:["test method descorator"]
The result-xzm:test method descorator
總結:個人感覺廣發證劵團隊在這里(揭秘Angular2這本書中)對方法裝飾器講得比較粗,對TypedPropertyDescriptor對象基本一筆帶過,
但是示例中卻用到了該對象內部的很多東西,使人跟本無從知道方法裝飾器具有何作用。
18.4 訪問器裝飾器:訪問器裝飾器聲明在一個訪問器聲明之前。訪問器裝飾器應用於訪問器的屬性描述符(),並且可以用來監視,修改或替換一個訪問器定義。
注意:TypeScript不允許同時裝飾一個成員的set或者get訪問器。取而代之的是,一個成員的所有裝飾必須應用在文檔順序的第一個訪問器上。這是因為,
在裝飾器應用於一個屬性描述符時,它聯合了get和set訪問器,而不是分開聲明的。
訪問器裝飾器表達式會在運行時當做函數被調用,傳入下列3個參數:
1.target:對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
2.properrtyKey:方法(成員)的名稱。
3.descriptor:成員的屬性描述符。
訪問器裝飾器基本和方法裝飾器一樣,除了需要注意上面提到的不允許同時裝飾一個成員的set和get訪問器以外。
18.5 屬性裝飾器:屬性裝飾器聲明在一個屬性聲明之前,屬性裝飾器表達式會在運行時當做函數被調用,傳入下列兩個參數:
1.target:對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
2.property:成員(屬性)的名字。
注意:屬性描述符不會作為參數傳入屬性裝飾器,這於TypeScript是如何初始化屬性裝飾器有關。因為目前沒有辦法在頂一個原型對象的成員時描述一個實例的屬性,
並且沒辦法監視或修改一個屬性的初始化方法。因此屬性描述符只能用來監視類中是否聲明了某個名字的屬性。
18.6 參數裝飾器:聲明在一個參數聲明之前(用於的類的構造函數或方法聲明),參數裝飾器表達式會在運行是被當做函數調用。
1.target:對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
2.propertyKey:成員的名字。
3.parameterIndex:參數在函數參數列表中的索引。
注意:參數裝飾器只能用來監視一個方法的參數是否被傳入。參數裝飾器在Angular中被廣泛使用,特別是結合reflect-metadata庫來支持實驗性的Metadata API。
參數裝飾器的返回值會被忽略。
18.7 裝飾器組合:TypeScript支持多個裝飾器同時應用到一個聲明上,實現多個裝飾器復合使用,語法支持從左到右,或從上到下書寫。
在TypeScript里,當多個裝飾器應用在一個聲明上的時候,會進行如下步驟的操作:
1.從左到右(從上到下)依次執行裝飾器函數,得到返回結果。
2.返回的結果會被當做函數,從左到右(從上倒下)依次調用。
19. 范型:基本和C#一致,ts的范型除了可以用於類上定義,還可以用於函數上定義。
20. TypeScript相關
20.1 編譯配置文件:tsc編譯器有很多命令行參數,都寫在命令行上會十分繁瑣。tsconfig.json文件正是用來解決這個問題。當運行tsc時,編譯器從當前目錄向上搜索tsconfig.json
文件來加載配置,類似於package.json文件的搜索方式..
20.2 TypeScript的一些語法糖
1.類型別名:類型別名聲名 type sn= number | string; //sn就代表number和string類型.
2.使用interface往另一個interface里面添加額外成員.
如:interface Foo{ x: number;}
interface Foo{ y:number;}
let a:Foo...;
console.log(a.x+a.y); //ok
3.同上,interface還可以往一個類里面添加額外成員.但是不能用interface為類型別名里添加額外成員.
4.聲明合並:是指編譯器將針對同一個名字的多個獨立聲明合並為單一聲明.和並后的聲明同時擁有原先多個聲明的特性.,其實這就包含了上面2,3兩條.
注意:接口的非函數成員必須是唯一性,合並時不能出現重復的.對於函數成員,每個同名函數聲明都會被當作函數重載,同時后面接口的函數比前面接口的函數具有更高的優先級.
5.名稱空間合並:與接口合並相似,同名的命名空間也會合並其成員.命名空間可以與其他類型的聲明進行合並,只要命名空間的定義符合將要合並類型的定義,合並后包含兩者的聲明類型.
示例1:
class Album { label:Album.AlblumLabel }
namespace Ablum{
export class AlbumLabel{};
}
必須導出AlblumLabel類,好讓合並的類能訪問.合並的結果是一個類並帶有一個內部類.
示例2:
function buildLabel(name:string):string{
return buildLabel.prefix+name+buildLabel.suffix;
}
namespace buildLabel{
export let prefix="";
export let suffix="hello,";
}
6.非法合並:目前,類不能與其他類或變量合並.
7.全局聲明:declare關鍵字可以將對象聲明為全局.