- TypeScript是微軟開發的,基于類的面向對象編程,其文件以 .ts 為后綴名;
- TypeScript是JavaScript的超集,完全兼容JavaScript代碼;
- TypeScript只存活于編譯階段,編譯為JavaScript之后,在瀏覽器/Node環境下才能運行;
- TypeScript的安裝與編譯
npm i -g typescript tsc helloworld.ts
- 默認情況下,編譯生成的js文件輸出到當前目錄下;
-
tsc helloworld.ts --outDir ./dist
:指定編譯后的js文件輸出到dist目錄。
tsconfig.json
tsc --init //生成TS的配置文件tsconfig.json
{
"compileOnSave": true, //自動編譯并保存
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"outFile": "./bundle.js",
"outDir": "./dist",
"strict": true,
......
"moduleResolution": "node", //按node編譯
"removeComments": true, //生成JS代碼后,移除注釋內容
"sourceMap": true, //配合后期調試
"emitDecoratorMetadata": true, //支持元數據,裝飾器需要使用
"typeRoots": [] //為TS編譯器指定檢查類型的依據文件
},
"include": [],
"exclude": []
}
-
tsconfig.json
:用于配置tsc
的編譯配置選項,如js文件的輸出目錄; - 當
tsc
不指定要編譯的ts文件時,編譯器會從當前目錄開始逐級向上查找tsconfig.json
- 當指定了編譯的ts文件時,
tsconfig.json
會被忽略; -
--project(-p)
:指定一個包含tsconfig.json
的目錄來進行編譯。
編譯選項:compilerOptions
-
outDir
:編譯生成的js文件的輸出目錄,當前目錄(./)
就是tsconfig.json
的所在目錄; -
outFile
:合并輸出到一個JS文件中,合并文件的順序為加載和依賴順序; -
module
:編譯后的js所使用的模塊化系統,none、commonjs、es2015、amd、esnext ...
-
target
:指定編譯后的js對應的ECMAScript版本,es3、es5、es6、es2015、es2016、es2017 ...
指定要編譯文件
- 執行
tsc
命令,但不指定任何ts文件時,默認會編譯當前項目中的所有ts文件; -
include
:指定要編譯的ts文件目錄"include": [ "./src/" ] //編譯src目錄下的ts文件
- 使用
glob
模式,類似于正則表達式,**/
遞歸匹配任意子目錄; -
*
匹配0或多個字符,?
匹配一個任意字符,但都不包括目錄分隔符; -
./src/*
:只編譯src目錄下的ts文件,不包括src的子目錄; -
./src/**/*
:遞歸編譯src目錄下的ts文件,包括子目錄;
- 使用
-
exclude
:指定不編譯的ts文件目錄,默認已經排除了node_modules
和outDir
目錄
數據類型
- let 變量: 類型
let input: HTMLInputElement = document.querySelector('uname'); let value: number = Number(input.value) + 10; let a: string; a = 1; //報錯
- TS的類型:
數字、字符串、布爾型、null、undefined、數組、元組、枚舉、void、any、Never
- 基本類型與包裝類型
- 基本類型:
string、number、boolean
- 包裝類型:
String、Number、Boolean
- 基本類型可以直接賦值給對應的包裝類型,但反之不行。
let s: String = 'nodejs';
- 基本類型:
- 布爾類型
var flag:boolean = true; flag = false;
- 數字類型
var n:number = 12;
- 字符串類型
var s:string = 'hello ts';
- 數組類型
- 基本語法:
var arr:number[] = [1, 2, 3]; //元素類型為number的數組
- 泛型方式:
var arr:Array<number> = [1, 2, 3];
- 基本語法:
- 元組類型:數組的一種,可以存儲多種類型的元素,但 順序不可以顛倒,長度不可以違規,是固定的;
let tup:[number, string] = [11, 'hello']; tup[0] = 20; tup[1] = '元組';
v2.6
之前,超出規定個數的元素稱作越界元素,但是只要越界元素的類型是聲明類型中的一種即可,屬于聯合類型,所以沒問題。但v2.6
之后,要求元組賦值的類型和個數都必須保持一致。 - 枚舉類型
enum 枚舉名{ 標識符[=整型常數], 標識符[=整型常數], 標識符[=整型常數], }
- 標識符可以加引號,也可以不加,
[]
表示可選參數
enum Flag { success=1, error, 'undefined'=-1 }
- 使用枚舉
let f:Flag = Flag.success; console.log(f); // 1
- 如果標識符都沒有賦予整型常數,則默認為從
0
開始的角標。
- 標識符可以加引號,也可以不加,
- 任意類型
var num:any = 123; num = false;
-
null
和undefined
,定義了變量,但并未賦值,默認是undefined
var num:undefined; console.log(num); // undefined, var num:null = null; console.log(num); // null
- 復合類型
var num:number | undefined | null; // num 可以是數字類型,也可以是undefined/null console.log(num); // undefined num = 100; console.log(num); // 100
-
void
類型:表示沒有任何類型,一般用于定義沒有返回值的函數;function run():void { console.log(123) }
- never類型:表示從不會出現的值,即聲明為never類型的變量只能被never類型所賦值;
var num:never; num = (()=>{ throw new Error('some happend'); //拋出異常,不屬于任何已知類型 })();
- 類型推導
- 有時候不一定強制使用類型聲明,TS會根據語境進行類型推導;
- TS的變量初始化推導
let a; a=1; //變量a 是number類型,不允許再賦予其他類型的值;
- TS的上下文推導
btn.onclick = function(e) {} //e: MouseEvent btn.onkeydown = function(e) {} //e: KeyboardEvent
- TS會根據當前綁定的事件,推導出回調函數的第一個參數類型
MouseEvent/KeyboardEvent
函數
- 函數聲明
function fn(x: Type, y: Type): Type { ... } function fn(x: number, y: number): number { //參數為number,返回值為number return x+y; } //匿名函數 let fn = function(x: number, y: number): number { return x+y; }
- 函數表達式
let fn:(x:Type, y:Type) => Type = function(x:Type, y:Type){ ... } let fn: (x: number, y: number) => number = function(x: number, y: number) { return x+y; } // 函數體function的參數類型可以省略,ts會進行類型推導 let fn: (x: number, y: number) => number = function(x, y) { return x+y; }
- 形參的對象約束
- 如果傳入的是匿名對象,其屬性必須與約定屬性一致;
function print(label:{name:string}):void { console.log('print ', label.name); } print({name:'Machel'}); //print Machel
- 如果不是匿名對象,則只需要包含約定的屬性即可;
let obj = { name: 'Machel', age: 20 } print(obj); //print Machel
- 可選參數:與ES6保持一致,必須配置在形參的末尾,且默認值為
undefined
function run(name:string, age?:number):string { return 'hello ts'; } run('Jack'); run('Jack', 20);
- 默認參數:指定形參的默認值,也必須配置在形參的末尾
function run(name:string, age:number=20):string { return 'hello ts'; }
- 如果手動指定了參數的默認值,則不能再聲明為可選參數
?
- 對于默認參數,可以利用TS的自動類型推導,從而不聲明類型。
- 如果手動指定了參數的默認值,則不能再聲明為可選參數
- 剩余參數:三點運算符
function sum(name:string, ...rest:number[]):number { //name是一個必傳參數(字符串類型),其余所有參數(number類型)都被封裝在 rest 數組中 let sum:number = 0; for(let i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } sum('Machel', 1, 2, 3); sum('Machel', 1, 2, 3, 4, 5);
- 函數重載:TS需要兼容ES5和ES6,所以TS的函數重載與Java的函數重載略有不同;
- 參數個數相同
function run(name:string):string; function run(age:number):string; function run(sn:any):any { if(typeof sn === 'string') { // string return 'name is ' + sn; } else { // number return 'age is ' + sn; } } run('Jackson'); // name is Jackson run(12); // age is 12 run(true); // 編譯報錯
- 參數個數不同:借助可選參數
function run(name:string):string; function run(name:string, age:number):string; function run(name:any, age?:any):any { if(age) { // age 存在 return `name: ${name}, age: ${age}`; } else { // age 不存在 return `name is ${name}`; } }
- 函數的
this
- ts的函數中,
this
默認指向any
,ts不能對any
類型提示任何屬性和方法; - 在
tsconfig.json
中,取消this
默認指向any
的設置:
"compilerOptions": { "noImplicitThis": true }
- 對于某些情況,如DOM事件,回調函數的
this
默認指向DOM對象,TS自動推導。
- ts的函數中,
類
ES5定義類
- 構造函數
function Persion() { this.name = 'Machel'; this.age = 20; this.show = function() { console.log(this.name, this.age); } } var p = new Persion(); p.show(); // Machel 20
- 在原型鏈上擴展屬性和方法
Persion.prototype.sex = 'male'; Persion.prototype.work = function() { console.log(this.name + 'is work!'); } p.work();
- 原型鏈上的屬性和方法會被多個實例共享,而構造函數中的屬性和方法只是拷貝一份給每個實例;
- 靜態方法
Persion.run = function() { console.log('run'); } Persion.run();
- 繼承:原型鏈、對象冒充,以及兩種模式的組合
- 對象冒充
function Web() { Persion.call(this); // Web繼承Persion } var w = new Web(); w.show(); // Machel 20 w.work(); // 報錯:對象冒充不能繼承原型鏈上的屬性和方法
- 原型鏈繼承
function Web() { } Web.prototype = new Persion(); w.show(); // Machel 20 //但是這種方式無法給父類傳參 function Persion(name, age) { this.name = name; this.age = age; this.show = function() { console.log(this.name, this.age); } } Web.prototype = new Persion(); var w = new Web('Machel', 22); w.show(); //undefined undefined,接收不到參數!
- 組合模式
function Web(name, age) { Persion.call(this, name, age); } Web.prototype = new Persion(); 或 Web.prototype = Persion.prototype; var w = new Web('Machel', 22); w.show(); // Machel 20 w.work(); // Machel is work!
- 需要百度,詳細看看ES5中的類
TS的類
- TS的類與ES2015(es6)中的
class
類似,同時新增了一些實用特性; - 類的定義
class Person { name:string; //屬性,默認訪問修飾符為public,默認值為undefined constructor(name:string) { this.name = name; } getName():string { return this.name; } run():void { console.log(this.name + ' 在 Person'); } } var p = new Person('Jackon'); console.log(p.getName()); // Jackon p.run(); // Jackon 在 Person
- 類的繼承:
extends
,單繼承class Web extends Person { constructor(name:string) { super(name); //必須先初始化父類的構造器 } run():void { //覆寫父類的方法 console.log(this.name + ' 在 Web'); } } var w = new Web('Machel'); console.log(w.getName()); // Machel w.run(); // Machel 在 Web
- 修飾符:
public、protected、private、readonly
-
public
:公有,在類內部、子類、類外部(對象)都可以訪問,默認修飾符 -
protected
:保護類型,在類內部、子類可以訪問,類外部(對象)不能訪問; -
private
:公有,在類內部可以訪問,子類、類外部(對象)不能訪問; -
readonly
:只讀,對象只能獲取,不能重新賦值; - 在構造函數的參數上使用修飾符,表示同時在類中創建該屬性,該屬性不能在類中預定義;
constructor(public age: number){ //為Person創建屬性age:public age: number; this.age = age; }
-
- 存取器:
setter/getter
的簡寫形式private _age: number = 10; get age(): number { //訪問:p1.age; return this._age; } set age(age: number) { //訪問:p1.age = 20; this._age = age; }
- 靜態屬性、方法:
static
修飾,靜態方法中沒有this
,所以只能訪問靜態屬性;class Person { public name:string|undefined; static age:number = 20; static print() { console.log('print ' + Person.age); } } Person.age; // 20 Person.print(); //print 20
- 多態:繼承的一種表現,父類引用指向子類對象!
var p:Person = new Web('Machel'); p.run(); // Machel 在 Web
抽象類
abstract
:修飾抽象類和抽象方法
- 抽象類不能直接實例化,抽象方法不包含具體實現;
- 抽象類中可以同時包含抽象方法和普通方法,但抽象方法只能放在抽象類中;
- 抽象類的子類如果不是抽象類,則必須實現父類的抽象方法!
abstract class Animal { public name:string; constructor(name:string) { this.name = name; } abstract eat():any; } class Dog extends Animal { constructor(name:string) { super(name); } eat() { console.log(this.name + ' is eat!') } } var d:Animal = new Dog('dog'); d.eat(); // dog is eat!
接口:interface
- 屬性接口,約束函數的對象形參
interface Options { width: number, height: number } function print(opts: Options){ console.log(opts.width, opts.number); } print({width:100, height:50})
- TS類型檢測器只會檢查接口所定義的規則屬性是否存在,并不會檢查屬性的順序;
print({height:50, width:100})
- 當以匿名對象的方式傳入時,必須嚴格遵守接口規則,不能有多余屬性;
print({ firstName: 'Jack', secondName: 'Machal', age: 1 //編譯報錯:匿名對象的形式傳入時,只能包含接口中約束的屬性 });
- 如果傳入的對象不是一個匿名對象,那么只需要包含接口規則的屬性即可;
var obj = { firstName: 'Jack', secondName: 'Machal', age: 1 } print(obj); //編譯通過:Jack Machal
-
as
斷言可以繞開TS檢測
print({ width: 100 } as Options); //編譯通過
- 可選屬性的接口
interface FullName { firstName:string; secondName?:string; //可選屬性 } print({ firstName: 'Jack' //不傳secondName屬性 })
- 函數接口
interface Func { (key:string, value:string):string; } var fn:Func = function(key:string, value:string):string { return key + value; } fn('Jackson', '123456'); //Jackson123456
- 可索引接口:數組、對象的約束,也就是一組
key-value
的數據,數量是不確定的,其中的key具有某種特性;- key的特性在于:只能是
string
或number
- 數組約束
interface UserArr { [index:number]:string //索引為number類型,值為string類型 } var arr:UserArr = ['aaa', 'bbb'];
- 對象約束
interface UserObj { [index:string]:string //屬性名和屬性值都是string類型 } var obj:UserObj = {name:'Joker', age:'20'}
- TS類是不允許對象自己直接擴展屬性和方法的,但可以通過接口去擴展!
class Person { name = 'Mack' } let p = new Person(); p.run = function(){ } //編譯報錯! interface Person { [attr: string]: any } p.fly = function() { //編譯通過 console.log('fly: ', this.name); } p.fly(); // fly: Mack
- key的特性在于:只能是
- 類類型接口:對類的約束,有點類似于抽象類
interface Animal { name:string; eat(foot:string):void; } class Dog implements Animal { name:string; constructor(name:string) { this.name = name; } eat() { //可以只實現接口要求的方法,忽略要求的參數 console.log('eat...'); } }
- 接口的繼承
interface Animal { eat():void; } interface Person extends Animal { work:void; } class Web implements Person { public name:string; constructor(name:string) { this.name = name; } eat() { console.log('eat...'); } work() { console.log('work...'); } }
- 一個類可以同時實現多個接口,但只能繼承一個類
class A extends B implements C,D { }
泛型
- 泛型變量
function getData<T>(value:T):T { return value; } getData<number>(123); //number類型 getData<string>('Machel'); //string類型 function getData3<T>(value:T):void { console.log(value) } getData<number>(123); // 123
- 泛型也可以有多個
function fn<T, S>(a:T, b:S): [T, S] { }
- 還可以是數組形式
function fn<T>(a: T[]): T[] { } function fn<T>(a: Array<T>): Array<T> { }
- 泛型類
class Min<T> { public list:T[] = []; add(value:T):void { this.list.push(value); } min():T { var m = this.list[0]; ... return m; } } var n = new Min<number>(); n.add(2);
- 泛型作為一種類型
let fn: <T>(x: T, y: T) => number = function(x, y) { return Number(x) + Number(y); }
- 泛型接口
- 方式一
interface IFn { <T>(x: T): T; } var fn:IFn = function<T>(x:T):T { return x; } fn<string>('Joker'); //Joker
interface IFn<T> { (x: T, y: T): number } let fn: IFn<string> = function(x, y) { return Number(x) + Number(y); }
- 方式二
interface IFn<T> { (x:T):T; } function fn<T>(x:T):T { return x; } var myGet:IFn<string> = fn; myGet('Joker');
- 泛型約束:
extends
,約束泛型的類型范圍- 約束泛型為HTML節點對象
function fn<T extends HTMLElement> (ele: T) { }
- 配合接口使用
interface Len { length: number } function fn<T extends Len> (e: T) { } fn(1); //編譯報錯:number類型沒有實現 Len 接口,也不具備 length 屬性 fn('2'); //string類型實現了 Len 接口
- 類類型
- 如何讓一個外部函數成為創建對象的工廠
function getArray(constructor: Array) { return new constructor(); } let arr = getArray(Array); //編譯報錯
- 形參constructor表示Array類型的對象,而不是一個Array的構造函數,所以無法創建對象;
-
{new()}
:表示構造函數類型
function getInstance(constructor: {new()}) { return new constructor(); } let arr = getInstance(Array); //通過TS檢查,創建一個數組對象
- 限制構造函數的類型:
function getInstance(ct: {new(): Array<string>}) { return new ct(); //只能創建Array<string>類型的對象 } function getInstance<T>(ct: {new(): T}) { //泛型 return new ct(); }
- 應用:數據庫操作的封裝
class MySqlDB<T> { // add(info:T):boolean { //...... 把info中的屬性插入到對應的表中 return true; } } class User { //數據庫表的映射對象 username:string; password:string; constructor(params:{ username:string, password:string }) { this.username = params.username; this.password = params.password; } } var u = new User({username:'Joker', password:'123456'}); var DB = new MySqlDB<User>(); DB.add(u);
模塊
- 內部模塊稱為命名空間,外部模塊稱為模塊;
- 模塊在自身作用域中執行,而不影響全局作用域,即模塊中的變量、函數、類等等對外部是不可見的;
- 外部要使用模塊內的數據,必須先讓模塊通過
export
暴露出里面的數據,外部再通過import
引入這些數據即可;
(外部)模塊
- 創建目錄
modules
,用于存放項目的模塊,創建一個模塊db.ts
var url = 'xxxxxx'; export function getData():any[] { return [ { title: '111' }, { title: '222' }, ] } export function save() { console.log('save...') }
- 在其他模塊中引入
db.ts
模塊中的方法import { getData } from './modules/db';
- 引入時起別名
import { getData as get } from './modules/db';
- 但是,
export、import
會被編譯為exports、require()
,瀏覽器默認不支持,需要使用node
命令執行,或者借助webpack
這樣的工具編譯為瀏覽器能執行的代碼; - 暴露方式二
export { url, getData, save }
- 暴露方式三:
export default
-
export
可以導出多次,而export default
只能導出一次
export default getData;
-
export default
導出的是什么數據,import
導入的就是什么數據
import getData from './modules/db';
-
命名空間
- 在一個模塊中,為了避免各種變量命名相沖突,可以將相似功能的函數、類、接口等放到獨立的命名空間內;
namespace A { interface Animal { name:string; } } namespace B { interface Animal { name:string; } }
- 命名空間內的數據默認是私有的,外部要使用這些數據,也必須通過
export
暴露出去;
namespace A { interface Animal { name:string; } export class Dog implements Animal { name:string='Joker'; eat() { console.log(this.name + ' eat'); } } } var d = new A.Dog(); d.eat(); // Joker eat
- 命名空間內的數據默認是私有的,外部要使用這些數據,也必須通過