TypeScript是JavaScript的超集,任何合法的JS程序都是合法的TypeScript程序前端
TypeScript經過向JavaScript增長可選的靜態類型聲明來把JavaScript變成強類型程序語言。node
靜態類型聲明可約束函數、變量、屬性等程序實體。jquery
TypeScript語言內部分爲三層:git
1. 語言層:實現全部TypeScript語言特性github
2.編譯層:執行編譯,類型檢查,將TypeScript代碼轉換成JavaScript代碼ajax
3.語言服務層:生成信息以幫助編譯器和其餘工具來提供更好的輔助特性typescript
全局安裝:npm install -g typescriptnpm
使用WebStrom調試json
1. File - Settings - Languages & Frameworks - TypeScript - TypeScript 路徑選取全局安裝路徑gulp
2. 項目 - New - tscofing.json 新增轉換項目
此時能夠轉換成js代碼,而後調試添加node指向js代碼便可。
使用命令行調試
tsc demo1.ts 能夠將TypeScript轉換爲JavaScript文件
能夠聲明一個變量的類型,當沒有類型聲明的時候,會嘗試檢查賦值。
var counter; var counter : number; var counter : number = 0;
boolean:true和false,表示是否正確
number:浮點數
string:文本
array:數組,let list : number[] = [] or let list:Array<number> = []
enum:枚舉,enum Color {Red,Blue}; var c : Color = Color.Red
any:任意JS值進行最小靜態化檢查,var list:any[] = [1,true,'']
void:沒有返回值
注:undefined 和 null,不能做爲變量類型賦值
聲明能夠存儲多種類型值變量
let path : string|string[]
能夠在運行時使用typeof或者instanceof運算符對類型進行驗證
容許在TypeScript代碼中建立一個不會被編譯到JavaScript中的變量。
若是但願調用一個未被定義的對象上的方法,可使用declare操做符建立一個環境聲明。
此時就不會有編譯錯誤
TypeScript,支持do while \ for in \ while \ for ,四種循環方式
函數經過三種方式聲明
//具名函數 function greet(name?:string){} //匿名函數 const greet = function(name?:string){} //箭頭函數 const greet = (name:string):string=>{}
包管理工具,只用於管理前端的依賴。所以Bower中的許多包也都針對前端作過優化。
安裝:npm install -g bower
建立配置文件:bower init 與 npm init 殊途同歸
安裝包:bower install jquery --save-dev
全部Bower包都保存在bower_components目錄下,也須要添加.gitignore中
TypeScript包含一個lib.d.ts文件,描述JS內建對象、文檔對象(DOM)、瀏覽器對象(BOM)的API。
擴展名爲.d.ts的文件是一種特殊的TypeScript文件,稱爲類型定義文件或描述文件。
一個類型描述文件一般對包含第三方庫API的類型聲明,使這些庫現存的JS庫與TS集成在一塊兒。
例如:TypeScript調用jQuery會獲得報錯。
$.ajax({}); //cannot find name '$'
須要添加jQuery描述文件的引用,例如
///<reference path="node_modules/@types/jquery/index.d.ts" />
tsd就是包含這些引用的開源項目DefinitelyTyped
tsd用來管理TypeScript應用中描述文件的工具,和npm\bower工具同樣,也有本身的配置文件tsd.json
安裝:npm install tsd -g
建立配置文件:tsd init
安裝描述文件:tsd install jquery --save
可是這種方法目前用不了,使用過渡方法:npm install --save @types/jquery
官網:http://definitelytyped.org/
自動化工具能夠自動完成開發中的重複任務,例如:編譯TS文件,壓縮JS文件等
現在最流行的兩個自動化工具就是,Grunt 和 Gulp
Grunt中使用文件做爲任務的輸入和輸出,Gulp中是使用流。Grunt插件使用鍵值對配置,Gulp插件使用代碼描述任務。
可讀性Gulp高於Grunt.
安裝:npm install -g gulp
開發依賴:npm install gulp --save-dev
項目根目錄建立gulpfile.js,用來配置gulp命令,內容以下:
let gulp = require('gulp'); gulp.task('default',function(){ console.log("Hello Gulp"); });
注意此時不能使用import引入,不然沒法進行測試。
執行gulp命令,會檢索當前目錄下的gulpfile.js,找到後執行default任務。
可使用Gulp插件中的tslint來檢查代碼質量。
安裝:npm install typescript --save-dev
安裝:npm install tslint --save-dev
安裝:npm install gulp-tslint --save-dev
添加新任務(沒起做用不知道爲何)
var gulp = require('gulp'); var tslint = require('gulp-tslint'); gulp.task('lint', function () { return gulp.src([ './source/**.ts', './test/**/**.test.ts' ]).pipe(tslint()) .pipe(tslint.report('verbose')); }); gulp.task('default', ['lint']);
添加兩個編譯TypeScript代碼的任務,一個編譯應用的邏輯代碼,另外一個編譯單元測試的代碼
安裝:npm install gulp-typescript --save-dev
建立新的gulp-typescript項目對象
var ts = require('gulp-typescript'); var tsProject = ts.createProject({ removeComments: true, noImplicitAny: true, target: 'ES5', module: "commonjs", declarationFiles: false });
上面代碼將TypeScript編譯器做爲依賴加載進來,而後建立tsProject對象。
包含TypeScript編譯器在編譯咱們的代碼時,須要帶上的參數。如今編輯應用源代碼
gulp.task('tsc', function () { return gulp.src('./source/**/**.ts') .pipe(ts(tsProject)) .js.pipe(gulp.dest('./source/js')); });
tsc任務會去查找./source/目錄及其子目錄下全部的.ts文件,而後將他們做爲一個輸入流傳遞給TypeScript編譯器
編譯器會使用tsProject對象的屬性做爲編譯時的參數,並將編譯後的JavaScript代碼保存在./source/js目錄下
針對不一樣地方的編譯,例如單元測試編譯。代碼格式同樣
再次修改default任務
gulp.task('default', ['tsc']);
編譯完TypeScript代碼後,會爲每一個TypeScript文件生成對應的JavaScript文件。
可是他們不能運行在瀏覽器中,由於在瀏覽器運行的惟一方法就是添加<script>標籤。
或者還有如下兩種方案
1. 使用RequireJS這樣的庫,經過AJAX來按需加載各個文件。這種作法稱爲異步模塊加載。
2. 將TypeScript模塊參數配置成CommonJS,而後使用Browerify這樣的工具,解析應用的模塊和依賴。最後生成一個包含應用裏全部模塊且高度優化的JS文件。
這裏使用CommonJS方法,由於Browserify和Gulp是高度集成的。
安裝:npm install browserify vinyl-transform gulp-uglify gulp-sourcemaps
導入這些包,而後對他們進行初始化。建立main.js輸入下方代碼
var browserify = require('browserify'); var transform = require('vinyl-transform'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var browserified = transform(function (filename) { var b = browserify({entries: filename, debug: true}); return b.bundle(); });
browserified函數會將普通的Node.js流轉換爲Gulp流。
實現實際的任務
gulp.task('bundle-js', function () { return gulp.src('./main.js') .pipe(browserified) .pipe(sourcemaps.init({loadMaps: true})) .pipe(uglify()) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./dist/source/js')); });
main.js 做爲入口,Browserify會從這個入口出發,追蹤應用裏全部任務的模塊和依賴,而後生成一個高度優化的JS文件的流。
接着會使用uglify插件來最小化輸出,這將會減小應用的加載時間,可是會讓BUG難以追蹤。
咱們生成一個source map文件來簡化追蹤BUG的過程。uglify會移出代碼中的空行和空格,並縮減變量名的長度。
source map使得咱們在追蹤bug時可以將最小化後的代碼映射到源文件中對應的地方。
咱們能夠輕鬆的追蹤一個最小化後的代碼的bug,在Chrome和Firefox的開發者工具裏,已經內建了對source map的支持。
目前,代碼還不能跑,由於如今裏面的任務是並行的。須要改爲按順序執行。
Gulp默認的是一步執行全部任務的,有三種方法讓一個任務同步執行
1.傳遞迴調函數
2.返回一個流
3.返回一個promise
//傳遞迴調函數 gulp.task('sync',function(cb){ setTimeout(function(){ cb(); },1000) ; }); //返回一個流 gulp.task('sync',function(){ return gulp.src('js/*.js') .pipe(concat('script.min.js')) .pipe(uglify()) .pipe(gulp.dest('../dist/js')) });
也能夠經過run-sequence的Gulp插件,能夠更好的控制任務執行順序。
安裝:npm install run-sequence --save-dev
var runSequence = require('run-sequence'); gulp.task('default',function(cb){ runSequence( 'lint', // lint ['tsc'], // 並行執行tsc ['bundle-js'], //並行執行bundle-js 'karma', // test 'browser-sync', // callback cb ); })
自動化測試工具讓咱們可以自動化執行應用裏面的單元測試。
經過自動化測試工具,能夠自動在多個瀏覽器內執行應用的測試套件,而不用打開瀏覽器運行測試。
自動化測試工具爲Karma,Karma能夠和多個流行的單元測試框架兼容。這裏的測試框架爲Mocha,Chai(斷言庫),Sinon(數據模擬框架)
安裝測試框架:npm install mocha chai sinon --save-dev
安裝Karma做爲開發依賴:npm install karma karma-mocha karma-chai karma-sinon karma-coverage karma-phantomjs-launcher gulp-karma --save-dev
var karma = require('gulp-karma'); gulp.task('karma', (cb) => { gulp.src('./dist/test/**/**.test.js') .pipe(karma({configFile: 'karma.conf.js', action: 'run'})) .on('end', cb) .on('error', (err) => { throw err; }) });
獲取了test及其子目錄下全部.test.js文件,而後把他們連同karma.conf.js文件一同傳遞給Karma插件。
須要在根目錄下建立一個名爲karma.conf.js的文件,
module.exports = function (config) { config.set({ basePath: '', frameworks: ['mocha', 'chai', 'sinon'], browsers: ['PhantomJS'], reporters: ['progress', 'coverage'], plugins: [ 'karma-coverage', 'karma-mocha', 'karma-chai', 'karma-sinon', 'karma-phantomjs-launcher' ], preprocessors: { './test/*.test.js': ['coverage'] }, port: 9876, colors: true, autoWatch: false, singleRun: false, logLevel: config.LOG_INFO }); };
配置文件說明了Karma應用根目錄、框架、瀏覽器、插件須要報告的測試執行期間的信息。
PhantomJS是一個無界面Web瀏覽器,使用它就無須打開一個真正的Web瀏覽器,而執行單元測試代碼。
在瀏覽器中運行咱們的應用,須要先安裝browser-sync包
安裝:npm install -g browser-sync
版本不一致,暫且擱置
能夠自動生成項目的文件結構、構建腳本等。
最流行的就是Yeoman,內置命令yo,同時也是包管理工具,基於模板生成項目
省城項目的模板在Yeoman中共稱爲生成器。生成器地址:http://yeoman.io/generators/
安裝:npm install -g yo
安裝生成器:npm install -g angular-typescript-gulp typings
安裝完成後結合yo命令一同使用:yo angular-typescript-gulp
接着根據提示填寫就能夠了。
參數能夠設置靜態類型聲明,返回值也能夠設置靜態類型聲明。好比下方代碼,就會編譯報錯,由於須要返回number
function named(name: string): number {
return "";
}
具名函數和匿名函數,行爲並不同。具名函數會優先識別,匿名函數除非表達式被賦值,不然不會被執行。
當咱們須要匿名函數返回自己時,能夠進行以下定義,來減小冗餘的類型聲明。
var greetUnnamed: (name: string) => string = function (name: string): string { return "Chenxy" }
若是不須要返回值能夠寫成
(name : string) : void;
注意:函數類型和變量類型,必須相同
可選參數能夠在參數名後方添加?號來防止編譯報錯
function add(foo?: number) { console.log(foo); }
能夠在類型聲明後面添加默認參數
function add(foo: number = 1) { console.log(foo); }
TypeScript也能夠寫rest參數來用一個數組接受多個參數。一樣也支持擴展運算符
function add(...foo: number[]) { console.log(foo); } add(1, 2, 3, 4); //[ 1, 2, 3, 4 ]
不一樣類型的方法能夠經過聲明一個函數的全部函數簽名,而後將逐個簽名一一實現,來達到函數重載的功效。
function foo(age: number): string; function foo(name: string): string; function foo(value: (number | string)): string { switch (typeof value) { case "string": return `string`; case "number": return `number`; } } console.log(foo(1)); //number console.log(foo("chenxy")); //string
實現簽名必須兼容全部的重載簽名。直接執行簽名函數會獲得一個編譯錯誤。
可使用T來定義泛型參數
///<reference path="node_modules/@types/jquery/index.d.ts" /> function getEntitytes<T>(name: string, entity: T): void { console.log(`name=${name},entity=${entity.get()}`); } class User { _name: string; get = () => this._name; constructor(name) { this._name = name; } } getEntitytes<User>("chenxy", new User("chenxy"));
TS中也可使用public或private訪問符。
能夠定義實現規則,接口的規則就是方法和屬性的簽名,繼承接口的類必須實現他們。
接口有兩點不一樣:
1. 接口能夠擴展其餘接口或者類
2. 接口能夠定義數據和行爲而不僅是行爲
使用extends能夠繼承某個父類,會繼承全部方法和屬性
class Teacher extends Person
使用super.method() 能夠調用父類方法,來實現重寫
若是子類有構造,則必需要構造中 super() 來構造父類,不然沒法使用。
使用implements能夠繼承多個接口,必須實現接口中定義的方法或屬性簽名。
interface IUser{ get():string; set:string; } class User implements IUser{ constructor(name) { this._name = name; } set: string; _name: string; get = () => this._name; }
使用extends能夠給泛型進行接口約束。
function getEntitytes<T extends IUser>(name: string, entity: T): void { console.log(`name=${name},entity=${entity.get()}`); } interface IUser{ get():string; set:string; } class User implements IUser{ constructor(name) { this._name = name; } set: string; _name: string; get = () => this._name; } getEntitytes<User>("chenxy", new User("chenxy"));
泛型約束不能進行多重約束,可是可使用組合接口來實現
須要定義組合接口,而且繼承接口A\B,而後泛型約束組合接口便可。
interface IA { A: string; } interface IB { B: string; } interface IC extends IA, IB { A: string; B: string; } class C implements IC { A: string = "CA"; B: string = "CB"; } class Person<T extends IC> { Entity: T; constructor(entity: T) { this.Entity = entity; } Con(): void { console.log(`A=${this.Entity.A},B=${this.Entity.B}`) } } const P = new Person(new C()); P.Con();
若是咱們要建立新的泛型(new T()),須要使用下方代碼
Copy(): T { var type: { new(): T; } return new type(); }
直接嵌套在類上面便可。namespace app {}
TypeScript使用模塊加載器來進行模塊加載。
模塊加載器是在模塊加載過程當中爲咱們提供更好控制能力的工具,能優化加載任務,好比異步加載文件或輕鬆合併多個模塊到單一的高度優化文件中。
不推薦使用<script>,由於瀏覽器不會異步加載此標籤。
使用ES6進行模塊加載時,須要使用export關鍵字來定義外部模塊。跟ES6語法一致
class UserModel { } export {UserModel}
引入使用 import ,跟ES6語法一致。
斷言:一個條件,是必須被測試確認的一段代碼的行爲是否與指望項目,與要求一致。
測試規範:軟件開發人員用來指代測試規範的一個術語,是一個詳細的清單,包含了須要測試的場景,如何被測試等。
測試用例:決定一個程序中的功能是否按照原始指望工做的一些條件。斷言是一個條件、測試用例是一組條件。
測試套件:許多測試用例的集合。一個測試套件能夠包含多個測試用例來覆蓋不少的場景。
測試監控:某些測試框架提供的功能,容許咱們包括一個函數,並記錄它的使用狀況。
替身:測試執行時被傳入但並無實際用到的對象。
測試樁:容許包括一個方法而後觀察它的使用狀況。
模擬:爲測試提供輸入來決定測試是否可以經過。
測試覆蓋率:指程序中有多大比例的代碼經過自動化測試被測試到。
必備工具
npm init
添加Gulp運行必要任務:npm install gulp -g
添加Karma自動執行測試:npm install --save-dev karma
Karma插件更容易建立測試覆蓋率報告:npm install --save-dev karma-coverage
Istanbul指出哪一行代碼在自動化測試中被測試到的工具。生成覆蓋率報告
Mocha測試框架庫,方便的建立測試套件、測試用例和測試規範:npm install --save-dev mocha karma-mocha
Chai支持測試驅動開發和行爲驅動開發的斷言庫:npm install --save-dev chai karma-chai
Sion.JS一個獨立的框架提供API能夠獨立測試一個組件:npm install --save-dev sinon karma-sinon
類型定義防止ts用第三方庫編譯錯誤:
npm install --save-dev @types/mocha
npm install --save-dev @types/chai
npm install --save-dev @types/sinon
npm install --save-dev @types/jquery
PhantomJS無顯示頁面的瀏覽器:npm install --save-dev phantomjs / npm install --save-dev karma-phantomjs-launcher
Selenium測試運行期,只運行端對端(E2E)測試的特定測試。
Nightwatch.JS自動化測試框架,使用Selenium網絡驅動API,完整的瀏覽器(E2E)測試解決方案。
npm install --save-dev gulp-nightwatch
npm install --save-dev selenium-standalone -g
selenium-standalone install (須要JAVA環境)
測試方面主要兩種風格或方法可選:測試驅動開發(TDD)和行爲驅動開發(BDD)
測試驅動開發
鼓勵開發者在寫程序代碼以前寫測試。使用TDD編碼包含如下基本步驟:
1. 編寫不經過的測試
2. 運行這個測試,保證不經過
3. 編寫應用代碼,讓測試經過。
4.運行這個測試,保證經過
5.運行全部其餘測試,保證沒有被破壞
6.重複以上步驟
TDD是被推薦的,由於他會極大程度的幫助你和你的團隊增長程序的測試覆蓋率。
行爲驅動測試
描述並闡明測試應該關注程序的需求而非測試的需求。鼓勵開發者少思考測試這件事而更多的思考整個程序。
單元測試:經過設置測試模擬和依賴注入來儘量的讓測試獨立。
部分集成測試和總體集成測試:用來測試一組組件或整個程序。
迴歸測試:用來確認程序錯誤是否被修復。
性能/加載測試:用來確認程序是否達到性能預期。
端對端(E2E)測試:用來測試一組組件或整個程序。
驗收測試(UAT):驗收是否符合用戶的全部需求
根目錄建立兩個文件夾source、test
npm install --save-dev gulp
npm install --save-dev browserify
npm install --save-dev vinyl-source-stream
npm install --save-dev vinyl-buffer
npm install --save-dev gulp-run
npm install --save-dev gulp-nightwatch
npm install --save-dev tslint
npm install --save-dev typescript
npm install --save-dev gulp-tslint
npm install --save-dev gulp-typescript
npm install --save-dev browser-sync
npm install --save-dev karma
npm install --save-dev gulp-uglify
npm install --save-dev gulp-docco
npm install --save-dev run-sequence
npm install --save-dev gulp-header
未完待續
只有TypeScript1.5或更高版本纔可使用。
安裝:
npm install --save-dev gulp gulp-typescript typescript
npm install --save reflect-metadata
建立gulpfile.js文件,添加編譯代碼的任務。
var gulp = require('gulp'); var ts = require('gulp-typescript'); var typescript = require('typescript'); var tsProject = ts.createProject({ removeComments: false, noImplicitAny: false, target: 'ES5', module: "commonjs", declarationFiles: false, emitDecoratorMetadata: true, typescript: typescript }); gulp.task("build-source", function () { return gulp.src(__dirname + "/file.ts") .pipe(tsc(tsProject)) .js .pipe(gulp.dest(__dirname + "/")); })
註解:一種爲類聲明添加元數據的方法,而後元數據就能夠被諸如依賴注入容器這樣的工具所使用()。
裝飾器:是ECMAScript7標準的特性,用來在代碼設計時註釋和修改類和類的屬性。
只須要關注裝飾器便可,由於他是真正的語言標準。
使用下面的類,來講明如何使用裝飾器。
class Person { public Name: string; public Surname: string; constructor(private name: string, private surname: string) { this.Name = name; this.Surname = surname } public saySomething(something: string): string { return `${this.Name} ${this.Surname} says: ${something}`; } }
裝飾器一共四中,分別用來裝飾:類、屬性、方法、參數
類裝飾器用來修改類的構造函數,若是返回undefined那麼類仍然使用原來的構造函數。
若是裝飾器有返回值,那麼返回值會被用來覆蓋類原來的構造函數。
建立一個logClass的類裝飾器。
function logClass(target:any){}
使用裝飾器須要在類上面 @logClass
若是已經聲明並使用一個裝飾器,通過TypeScript編譯後的代碼中會有一個__decorate的函數。
@logClass class Person { public Name: string; public Surname: string; constructor(private name: string, private surname: string) { this.Name = name; this.Surname = surname console.log('Person'); } public saySomething(something: string): string { return `${this.Name} ${this.Surname} says: ${something}`; } } function logClass(target: any) { var orginal = target; function construct(constructor, args) { var c: any = () => constructor.apply(this, args); c.prototype = constructor.prototype; return new c(); } var f: any = (...args) => { console.log('logClass'); return construct(orginal, args); } f.prototype = orginal.prototype; return f; } new Person("chen", "xy"); //logClass Person
類裝飾器接受一個參數,即類的構造函數。意味着target參數就是Person類的構造函數。
裝飾器先複製類的原有構造,而後定義一個名爲construct的工具函數來生成類的實例。
裝飾器用來爲元素添加一些額外的邏輯或元數據,咱們想要拓展一個函數的功能時。
須要往原函數上包一個新的函數,有額外邏輯,且能執行原函數的方法。
與類裝飾器類似,用來覆蓋類的方法。
若是返回的不是undefined,那麼返回值將會覆蓋方法的屬性描述對象。
被調用時,帶有如下參數:
包含了被裝飾方法的類的原型,即Person.prototype
被裝飾方法的名字,即saySomething
被裝飾方法的屬性描述對象,即Obejct
class Person { public Name: string; public Surname: string; constructor(public name: string, private surname: string) { this.Name = name; this.Surname = surname console.log('Person'); } @logMethod public saySomething(something: string): string { return `${this.Name} ${this.Surname} says: ${something}`; } } function logMethod(target:any,key:string,descriptor:any){ var orginalMethod = descriptor.value; descriptor.value = function(...args:any[]){ var a = args.map(a=>JSON.stringify(a)).join(); var result = orginalMethod.apply(this,args); var r = JSON.stringify(result); console.log('logMethod'); return result; } return descriptor; } const P = new Person("chen", "xy"); console.log(P.saySomething("c")); //logClass Person
建立被裝飾元素的副本。
建立一個新函數來替代被裝飾的函數,新函數除了調用原函數以外,還包含一些額外邏輯。
一個屬性裝飾器沒有返回值且沒有第三個參數
class Person { @logProperty public Name: string; public Surname: string; constructor(public name: string, private surname: string) { this.Name = name; this.Surname = surname console.log('Person'); } public saySomething(something: string): string { return `${this.Name} ${this.Surname} says: ${something}`; } } function logProperty(target: any, key: string) { var _val = this[key]; var getter = function () { console.log(`Get:${key} => ${_val}`); return _val; } var setter = function (newValue: any) { console.log(`Set:${key} => ${_val}`); _val = newValue; } if (delete this[key]) { Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); } } const P = new Person("chen", "xy"); console.log(P.saySomething("c")); //logClass Person
建立原屬性副本,聲明兩個函數:getter\settet
手動刪除原屬性,並使用Object.defineProperty來建立新屬性
接受三個參數
包含被裝飾參數的方法的對象
方法的名字
參數在參數列表中的索引
參數屬性沒有返回值,意味着不能覆蓋包含修飾參數的方法。
class Person { public Name: string; public Surname: string; constructor(public name: string, private surname: string) { this.Name = name; this.Surname = surname console.log('Person'); } public saySomething(@addMetadata something: string): string { return `${this.Name} ${this.Surname} says: ${something}`; } } function addMetadata(target: any, key: string, index: number) { var metadataKey = `Log_${key}`; if (Array.isArray(target[metadataKey])) { target[metadataKey].push(index); } else { target[metadataKey] = [index]; } } const P = new Person("chen", "xy"); console.log(P.saySomething("c")); //logClass Person
#