typescript指南 前言 typescript是angularjs2推薦使用的腳本語言。它由微軟2012年首次發佈。 一. typescript和javascript的區別 1.從遵循的規範上的角度: Javascript遵循ECMA5的規範,TypeScript是語法上對ECMA6的實現。 ...
typescript指南
前言
typescript是angularjs2推薦使用的腳本語言。它由微軟2012年首次發佈。
一. typescript和javascript的區別
1.從遵循的規範上的角度:
Javascript遵循ECMA5的規範,TypeScript是語法上對ECMA6的實現。
2.從功能上說:
TypeScript提供了類、模塊和介面來幫助構建組件,更方便寫面向對象的程式,所以被稱為更好的typescript。
3.從支持上說:
所有的瀏覽器都支持ES5及之前的javascript,而沒有瀏覽器支持typescript,所以需要編譯。另外typescript支持所有javascript的語法。
照片顯示了ES5、ES2015、ES2016以及typescript的關係。
二. typescript的安裝和helloworld程式
(1)npm安裝
npm install -g typescript@1.8 全局安裝了1.8版本的typescript編譯器和tsc程式,並且添加到環境變數中。為了確認正常,列印-v命令。
$ npm -v //首先看一下npm是不是新版本 $ npm install -g typescript@1.8. //全局安裝typescript $ tsc -v
顯示typescript的版本是:
(2)運行第一個ts程式。
第一種方式。
首先在1.hello.ts程式中輸入:
console.log("hello world!")
然後執行命令:
$ tsc 1.hello.ts //把ts文件轉換為等價的hello.js
最後運行node來執行js文件。
$ node 1.hello.js //列印出hello world。成功!
第二種方式。
先裝好typescript和ts-node.
$ npm install [email protected] -g //裝好ts $ npm install -g ts-node //裝ts-node模塊,選項是-g哦。
再運行命令執行文件。
$ ts-node 1.hello.ts //列印hello world,成功!
三. typescript從ES2015和ES2016引入的語法和特性
ES2015和ES2016有比較大的變化。
(1)ES2015的箭頭函數
由來:javascript有一級函數的特性,也就是說,函數能夠當做參數進行傳參,很好的用法不過太啰嗦,所以ES2015提供了箭頭函數,更加簡潔。
ts例子1:
var even=[3,1,56,7].filter(el=>!(el%2)); //對數組調用過濾器filter函數,!(el%2)只有偶數的取模操作為0,那麼整體就是true,所以el獲取的是偶數 console.log(even); //列印56咯。
通過運行ts-node命令,屏幕顯示結果為:
ts例子2:
var result=[1,2,3] .reduce((total,current)=>total+current,0); //reduce對每個數組元素比如1、2、3調用回調函數,初始值為0。 //回調函數的參數是total和current,total是上一次回調函數的值,第一次就為初始值0. // current是當前數組元素的值。返回值是total+current。 //那麼就清晰了,第一個total為0,當前元素是1,返回的0+1. console.log(result);
通過運行ts-node命令,屏幕顯示結果為:
ts例子3:
var data=[ {price:10,total:70}, {price:94,total:340}, {price:14,total:34} ]; var sorted=data.sort((a,b)=>{ //a、b為依次遍歷的數組元素 //這裡的對象是函數體 var diff=a.price-b.price; if(diff!==0){ return diff; } return a.total-b.total; }) console.log(sorted);
通過運行ts-node命令(編譯),屏幕顯示正確的排序結果為:
ts例子4:
function MyComponent(){ this.age=42; console.log(this); setTimeout(()=>{ //箭頭函數的特性,它的執行上下文指向外層的代碼,即組件實例的this對象 this.age+=1; console.log(this.age); },100); //等待100ms,age加1等於43,並列印出來 } new MyComponent();
通過編譯,屏幕顯示:
(2)ES2015和ES2016中的類
首先說明一下,ES6就是ES2015。ECMAScript 6(以下簡稱ES6)是JavaScript語言的下一代標準。因為當前版本的ES6是在2015年發佈的,所以又稱ECMAScript 2015。
然後ECMAScript 2016就是ES7。
ES6的類依然是使用構造函數和基於原型的繼承,但是語法更加方便和簡潔。
下麵是ES2016定義類的語法:
class Human{ static totalPeople=0; _name;//ES2016的屬性定義語法 constructor(name){ this._name=name; Human.totalPeople+=1; } get name(){ return this._name; } set name(val){ this._name=val; } //name屬性的get和set方法 talk(){ return "Hi,I'm"+this.name; } } class Developer extends Human{ _languages;//ES2016的屬性語法 constructor(name,languages){ super(name); this._languages=languages; } get languages(){ return this._languages; } talk(){ return super.talk()+" And I Know "+this.languages.join('.'); } } var human=new Human("foobar"); var dev=new Developer("bar",["javascript"]); console.log(dev.talk());
喜歡!除了類的定義體是對象外,屬性和方法的書寫和java很像,我喜歡的方式。
通過編譯,屏幕顯示結果為:
(3)定義塊級作用域中可見的變數
java和c++是塊級作用域。
只列舉代碼,表現為2點,第一個是特定代碼塊的變數只能代碼塊內部可見,第二,嵌套在內部的代碼塊中也可見。
public class Demo{ // 屬性塊,在類初始化屬性時候運行 { int j = 2;// 塊級變數 } public void test1() { int j = 3; // 方法級變數 if(j == 3) { //j變數在代碼嵌套的內部if語句中可見 int k = 5; // 塊級變數 } }
javascript是函數作用域。
var fns=[]; for(var i=0;i<5;i+=1){ fns.push(function(){ console.log(i); }) } fns.forEach(fn=>fn());
列印結果,很奇怪。
ES6代碼:
var fns=[]; for(let i=0;i<5;i+=1){ fns.push(function(){ console.log(i); }) } fns.forEach(fn=>fn());
列印結果:
(4)使用ES2016裝飾器進行元編程。
裝飾器是ES2016的一個提案,它的依據是“在設計階段可以對類和屬性進行註釋和修改”。在angular2很常用,可用來定義組件、指令以及管道,並且還能配合依賴註入機制來使用。
首先,裝飾器能幹嗎。
裝飾器典型的用法是把方法和屬性標記為過期,另一個應用場景是聲明式語法,從而實現面向切麵的編程。其實呢,裝飾器只是一個語法糖而已。裝飾器目前並沒有得到真正的使用。
Experimental support for decorators is a feature that is subject to change in a future release.
//編寫了Person類,它只有一個getter,名字為kidCount class Person{ //kidCount有一個裝飾器nonenumerable @nonenumerable get kidCount(){ return 42; } } //裝飾器函數接收3個參數,最後返回descriptor屬性。 function nonenumerable(target,name,descriptor){ descriptor.enumerable=false; //可枚舉性 return descriptor; } var person=new Person(); for(let prop in person){ console.log(prop); }
對應的ES5語法類似於:
descriptor=nonenumerable(Person.prototype,'kidCount',descriptor); //descriptor Object.defineProperty(Person.prototype,'kidCount',descriptor);
接下來,介紹angular 2裝飾器的用法。
@Component({ selector:"app", providers:[NameList], tempalteUrl:"./app.html", directives:[RouterOutlet,RouterLink] }); //這個定義好拽呀,直接寫好@+Component+選項對象 @RouterConfig({ {path:"/",component:Home,name:'home'}, {path:"/",component:About,name:'about'} }); //寫好@+RouterConfig+選項對象
export class App{}
如果裝飾器需要接收參數,那麼就定義為接收參數的函數,然後由函數返回真正的裝飾器。
(5)使用ES2015編寫模塊化的代碼
angular 1.x引入了一套模塊系統,不過並不支持懶載入特性。angular 2種充分利用了ES2015提供的模塊系統。ES2015提供了聲明式API,以及使用模塊載入器的命令式API。
語法分為export和import兩個方面。
第一,看一個簡單的DEMO:
math.ts:
export function square(x){ return Math.pow(x,2); } export function log(x){ return Math.log(x); } export const PI=Math.PI;
math2.ts,更簡潔的寫法而已:
function square(x){ return Math.pow(x,2); } function log(x){ return Math.log(x); } const PI=Math.PI; export {square,log,PI}
app.ts調用,要編譯的是app.ts哦:
import {square,log} from "./math"; console.log(square(2)); console.log(log(100));
屏幕顯示效果:
第二,ES2015模塊化語法帶有隱式的非同步行為。
比如說,
A模塊依賴於B、C模塊。當用戶請求A模塊,JS模塊載入器會先載入B和C模塊,才能調用A模塊。這裡B和C模塊都是非同步載入的。
第三,典型的應用場景會給導出的內容起一個名字。
使用別名導入整個模塊的DEMO:
import * as math from "./math"; //as語法咯 console.log(math.square(2)); console.log(math.log(100));
第四,預設導出。
模塊導出使用了export default語法,是一種帶名字的導出。
基本的預設導出DEMO:
math3.ts:
export default function cube(x){ return Math.pow(x,3); //預設導出的名字是cube } export function square(x){ return Math.pow(x,2); }
app3.ts:
import cube from "./math3"; //等同於import {default as cube} from "./math3 console.log(cube(3));
顯示結果正常:
預設導出混合其他導出的DEMO:
math3.ts:
export default function cube(x){ return Math.pow(x,3); //預設導出的名字是cube } export function square(x){ return Math.pow(x,2); }app4.ts:
import cube,{square} from "./math3"; console.log(square(2)); console.log(cube(3));
顯示結果OK:
(6)ES2015的模塊載入器
通過編程的方式載入app模塊執行main函數,使用System對象的import方法就好了。現在代碼因為缺乏配置項,所以還運行不起來。
app.ts:
export function main(){ console.log(2); }
init.js
System.import("./app") .then(app=>{ app.main(); }) .catch(error=>{ console.log("致命的錯誤"); });
四: 發揮靜態類型的優勢
有了靜態類型,那麼IDE開發環境除了避免輸入錯誤的語法高亮,還提供精確靜態分析的建議。很棒。
typescript的所有類型包含幾類:
● 原生類型
● 組合類型
● Object類型
● 泛型
● any類型
(1)使用顯式類型定義
除了webstorm報類型(type)錯誤,運行編譯命令,typescript 也報錯 Type 'string' is not assignable to type 'number' 。那麼就是說,一旦foo設置了類型,就不能賦值為其他類型了。
(2)any類型
any類型是所有其他類型的父類,代表可以擁有任何類型的值,類似於動態類型,一方面不會報錯,另一方面則放棄了typescript的優點了。
let foo:any; foo={}; foo="bar"; foo+=24; console.log(foo);//結果為"bar 24"。
(3)原生類型
就是javascript比較熟悉的Number、String、Boolean、Null、以及Undefined。而Enum是用戶自定義的原生類型,它是Number的子類。含義是枚舉用戶自定義的類型,由一系列有名稱的值也就是元素構成。
定義enum如下:
enum STATES{ CONNECTING, WAITING, CONNECTED } //定義枚舉類型 if(this.state==STATES.CONNECTING){ //通過點語法引入 }
(4)Object類型
首先,講更加通用的Array類型,它是Object的子類。
Typescript的數組,要求元素類型相同。都可以使用js的各種數組方法,比如push、join、splice等,也可以使用方括弧運算符對數組元素進行訪問。
數值型數組DEMO:
let primes:number[]=[]; primes.push(2); primes.push(3); console.log(primes);
any型數組:
let randomItems:any[]=[]; randomItems.push(1); randomItems.push("foo"); randomItems.push("{}"); console.log(randomItems);
屏幕結果為
第二,說Function類型,也是Object的子類哦。
javascript有兩種方式創建新函數:
//函數表達式 var isPrime=function(n){ } //函數聲明 function isPrime(n){ } //或者,使用箭頭函數 var isPrime=n=>{ //函數體 }
Typescript增加的是參數和返回值的類型。
函數表達式:
let isPrime:(n:number)=>boolean=n=>{ //整個函數賦給變數isPrime,參數是number類型的n,返回值是boolean類型的n,函數體在{}裡面 }
函數聲明:
function isPrime(n:number):boolean{ //參數為number類型,返回值為boolean類型 }
對象字面量的定義寫法:
let person={ _name:null, setName(name:string):void{ //參數是string類型,返回值是void this._name=name; } }
(5)定義類
typescript定義類,屬性的聲明式強類型的。
class-basic.ts:
class Human { static totalPeople=0; _name:string;//強類型的哦 constructor(name){ this._name=name; Human.totalPeople+=1; }; get name(){ return this.name; } set name(val){ this._name=val; } talk(){ return "HI,I'am"+this._name; } } let human=new Human("foo"); console.log(human._name);
列印結果為
成功!
(6)訪問修飾符
有3個。更好的實現封裝和更優雅的介面。
●public。public的屬性和方法在任何地方可以訪問。
●private。private的屬性和方法只能在類定義內部進行訪問。
●protected。protected的屬性和方法可以類定義內部訪問,也可以從子類訪問。
//typescript實現 class Human{ static totalPeople=0; constructor(protected name:string,private age:number){ //定義了一個protected型的屬性,名為name,類型為string //age屬性。好處是避免顯示式的賦值操作 Human.totalPeople+=1; } talk(){ return "Hi,I'm"+this.name; } } class Developer extends Human{ constructor(name:string,private languages:string[],age:number){ //顯式使用訪問修飾符,或者定義接收的參數類型,都可以混合使用的。 super(name,age); } talk(){ return super.talk()+" And I Know "+this.languages.join('.'); } } //創建developer類的一個新實例 let Dev=new Developer("foo",["javascript","Go"],42); //dev.languages=["java"];這行代碼會報一個私有屬性的錯誤 let human=new Human("foo",42); //human.age=42;由於私有屬性,所以報錯 //human.name="bar",會報一個protected錯誤。它只能在類類內部或者子類中訪問
(6)介面
介面定義了一組對象共同的屬性和方法,稱作介面簽名。要實現介面,那麼實現定義介面規定的所有屬性和方法。
關鍵字就2個,interface和implements。
interface Accountable1{ goIncome():number; accountNumber:string } class Value implements Accountable1{ constructor( public accountNumber:string){ this.accountNumber=accountNumber; } goIncome():number{ return 100; } } var extra=new Value("餘額"); console.log(extra.accountNumber);
列印為
介面實現,成功!
介面繼承
介面之間可以互相繼承。介面繼承另外一個介面,使用extends關鍵字。
interface-extends.ts:
interface Accountable2{
accountNumber:string;
getIncome():number;
}
interface Individual extends Accountable2{
ssn:string;
}
實現多個介面:
interface People{ age:number; name:string; } interface Accountable{ accountNumber:string; goIncome():number } class Person implements People,Accountable{ //實現多個介面,用逗號分隔 age:number; name:string; accountNumber:string; constructor(age:number,name:string,accountNumber:string){ } goIncome():number{ return 10; } } var person=new Person(10,"100","1000");
五: 使用類型參數編寫泛型代碼
可以寫類似java的泛型代碼,好精彩!使用“<T>”。能寫出更簡潔的代碼來。
(1)使用泛型類
定義一組類型的class。
class Nodes<T> { value:T; left:Nodes<T>; right:Nodes<T>; } let numberNode=new Nodes<number>(); let stringNode=new Nodes<string>(); numberNode.right=new Nodes<number>();//類型匹配 numberNode.value=42;//類型匹配 //numberNode.value="42";報錯,類型不匹配 //numberNode.left=stringNode;報錯,類型不匹配
(2)使用泛型函數
定義一組類型的函數。
function identify<T>(arg:T){ return arg; } interface Comparable{ compare(a:Comparable):number; } function sort<I extends Comparable>(arr:Comparable[]){ // }
(3)多重泛型
class Pair<K,V>{ key:K; value:V; } let pair=new Pair<string,number>() pair.key="foo"; pair.value=42;
好精彩!
五: 使用typescript的類型推斷機制簡化代碼
typescript可以猜測代碼中的靜態類型。好智能哦,所以可以用來省略一些代碼。
let answer=42; answer="42"; //會提示錯誤,因為一開始的賦值,typescript已經把它當做number類型了。 let answer; answer=42; answer="42"; //這個時候不會報錯,因為第一次聲明時,typescript給到的靜態類型是any。
(1)最常見的類型
let x=["42",42]; //x的類型推斷為any[]數組。
let x=[42.null,32]; //typescript的類型推斷為number[]。
(2)與上下文有關的類型推斷
這裡可以看到e並沒有規定類型,可是typescript就根據上下文推斷它為MouseEvent滑鼠事件。
六: 使用外部類型定義
儘管靜態類型很酷,但是我們使用的大部分前端類庫都是基於javascript構建的,都是動態類型。而typescript提供了額外的類型定義,來給編譯器提供提示。
(1)使用預定義的外部類型定義
step1:安裝typings工具
npm install -g typings
於是就安裝好了typings目錄,tsconfig.json、typings.json中,內容為:
//tsconfig.json { "compilerOptions": { "module": "commonjs", "target": "es5", "sourceMap": true }, "exclude": [ "node_modules" ] }
//typings.json { "dependencies": {} }
step2:創建基礎配置
typings init
step3:搜索
$ typings search module
step4:安裝is-builtin-module
$ typings install is-builtin-module --save
這個時候,typings.json的內容變為
{ "dependencies": { "is-builtin-module": "registry:npm/is-builtin-module#1.0.0+20161031191623" } }
typings目錄變為
(2)自定義外部類型
step1:定義好類庫的介面。
define-external-type.ts:
interface LibraryInterface{ selectElements(selector:string):HTMLElement[]; hide(element:HTMLElement):void; show(element:HTMLElement):void; }
step2:定義ts.d文件。
interface DOMLibraryInterface{ selectElements(selector:string):HTMLElement[]; hide(element:HTMLElement):void; show(element:HTMLElement):void; } declare var DOM:DOMLibraryInterface;
step3:DOM通過reference引入,編譯器就會找到對應的外部類型定義了。
///<reference path="dom.d.ts" /> var DOM={ selectElements:function(selector:string):HTMLElement[]{ return []; }, hide:function(element:HTMLElement):void { element.hidden=true; } };
這個時候,會報錯。直到完全實現定義的介面為止,如下:
///<reference path="dom.d.ts" /> var DOM={ selectElements:function(selector:string):HTMLElement[]{ return []; }, hide:function(element:HTMLElement):void { element.hidden=true; }, show:function(element:HTMLElement):void{ element.hidden=false; } };
這樣分離出來,就可以把全部的外部類型定義放在同一個文件里,方便管理了。