搞一搞TypeScript,做了一點筆記,奧里給,肝了兄弟們!
Part1內容
安裝typescript編譯器
全局安裝:npm install -g typescript
在命令行中查看ts編譯器版本判斷是否安裝成功。

TypeScript 文件默認以 .ts 為后綴,TypeScript 是 JavaScript 的擴展,所以 TypeScript 代碼要在 瀏覽器/Node 環境下運行,需要把 TypeScript 代碼編譯為 JavaScript 代碼。
ts初體驗

ts-node
這是一個基於Node.js的運行typescript的REPL環境,適用於typescript@>=2.7。
ts-node這個插件方便我們直接執行ts文件,不用手動執行ts編譯后的js文件,很方便。
全局安裝:npm install -g ts-node

tsconfig.json
當使用 tsc 並不指定 要編譯的ts文件 的情況下,會從當前運行命令所在的目錄開始逐級向上查找 tsconfig.json 文件。
tsconfig.json 文件用來配置 tsc 的編譯配置選項。
我們也可以通過 --project(-p) 來指定一個包含 tsconfig.json 文件的目錄來進行編譯。
編譯選項
{
"compilerOptions": {
"module": "ES2015",
"target": "ES5",
"outDir": "./dist"
},
"include": [
"./src/**/*"
]
}
compilerOptions字段定義了編譯相關設置
- module:指定編譯后的代碼要使用的模塊化系統
- target:指定編譯后的代碼對應的ECMAScript版本
- outDir:指定編譯后的代碼文件輸出目錄
- outFile:將輸出文件合並成一個文件(合並的文件順序為加載和依賴順序)
include字段指定了要包含的編譯文件目錄,它的值是一個目錄數組,使用glob模式
- *匹配0或多個字符(不包括目錄分隔符)
- ?匹配一個任意字符(不包括目錄分隔符)
- **/遞歸匹配任意子目錄
exclude:指定不要包含的編譯文件目錄,值也是一個目錄數組,類似include,默認會排除node_modules和outDir指定的目錄。

類型系統
類型注解(類型聲明、類型約束)
JavaScript是動態語言,變量隨時可以被賦予不同類型的值,變量值的類型只有在運行時才能決定。
在編碼(編譯)階段無法確定數據類型,會給程序在實際運行中帶來極大的隱患,不利於編碼過程中的錯誤排查。
使用類型注解就能夠在變量聲明的時候確定變量存儲的值的類型,用來約束變量或參數值的類型,這樣在編碼階段就可以檢查出可能出現的問題,避免把錯誤帶到執行期間。
語法
語法:let變量:類型
當變量接收了與定義的類型不符的數據會導致編譯失敗(警告)。


類型
官方文檔:https://www.tslang.cn/docs/handbook/basic-types.html
typescript中定義的類型有:
數字、字符串、布爾值
null、undefined
數組、元組、枚舉
void、any、Never
字符串&數字類型
let a: string;
a = 'asd';
// a = 1;
let b: string = 'test';
let c: String = 'test123'; // 可以把基本數據類型賦值給對應的包裝對象類型
let d: String = new String('test456');
// let e: string = new String('test789'); // 不可以把包裝對象類型賦值給基本類型
let f: number;
f = 123;
// f = '123'; // 報錯
string、number、boolean屬於基本類型
String、Number、Boolean:屬於對象類型
注意:
- 包裝類型可以賦值給對應包裝對象
- 包裝對象不可以賦值給對應基本類型
let s: String = 'some string...'; // 正確
let s: string = new String('some string...'); // 錯誤
數組
TypeScript像JavaScript一樣可以操作數組元素。 有兩種方式可以定義數組。 第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個數組:
let list: number[] = [1, 2, 3];
第二種方式是使用數組泛型,Array<元素類型>:
let list: Array<number> = [1, 2, 3];
/**
* 定義數組的方式有2種,第一種可以在元素后面接上[],表示由此類型元素組成的一個數組
* 第二種方式就是使用數組泛型,Array<元素類型>
*/
let list: number[] = [1,2,3];
console.log(list);
// console.log(list.push('4')); // 直接報錯 不能把字符串類型的數據添加到number類型的數組中
// 數組泛型創建方式
let list2: Array<string> = ['a','b','c'];
console.log(list2);
console.log(list2.push('1,2,3','456'));
// console.log(list2.push(1,2,3)); // 直接報錯 不能把number類型的數據添加到string類型的數組中
console.log(list2);
元組
與數組類似,允許表示一個已知元素數量和類型的數組,各元素的類型不必相同,對於下標內的數據,數據順序必須與聲明中的類型一一對應。
/**
* 與數組類似,但是可以存放多種不同類型
*/
let data1: [number, string, boolean];
// 注意:順序要對應
data1 = [1,'123',true]
聯合類型
多個類型中的一個,或的關系。
let a: string | number;
a = "m";
a = 1;
a = '1';
// a = false; // 報錯,不能把boolean類型分配給string或者number類型
console.log(a);
枚舉類型
使用枚舉可以為一組數據賦予友好的名字,enum Color {Red, Green, Blue},默認情況下,元素編號從0開始,也可以手動編號enum Color {Red=1...}
// let gender:number = 1; // 1:男,2:女
// if(gender==1) { // 容易忘記1表示的是什么
// }else {}
enum Gender {Male, Female}; // enum Gender {Male=0, Female=1};
if(Gender.Male) {
console.log("男");
}else {
console.log("女")
}
// enum Gender {Male=1, Female}; // Female會從2開始
類型推導
有的時候不一定需要強制使用類型聲明,在某些情況下 TS 可以根據語境進行類型推導。
變量初始化的時候TS 會根據變量初始化的時候賦予的值進行類型推斷。
上下文推斷的時候TS 也會根據上下文進行類型的推斷,比如在事件函數中,函數的第一個參數會根據當前綁定的事件類型推斷處理事件對象。
/**
* 其它類型
*/
// let a:undefined;
// a = undefined;
// 下面是可以的,可以把null賦值給其他類型,但是不能把其他類型的數據賦值給null
// let a: number;
// a = null;
// let b: any;
// b = false;
// console.log(b)
// 類型推導
// let c = 1; // ts會自動推導,c是number類型的
// c = 'm'; // 報錯
Part2內容

上面的代碼在頁面中沒有這個div元素的時候會報錯,因為document.querySelector('div')在頁面中沒有div的時候會返回一個null值,然后給這個null值添加顏色就會報錯,這種問題可以通過TS來解決。
注意:在js中,如果一個方法的返回值是一個對象,那么該方法在沒有返回值的情況下,接收的變量就會為null。
TS類似於ESLINT,通過定義各種類型檢測規則來約束代碼,減少隱形的錯誤。
函數
函數聲明
// 函數聲明寫法
function fn1(x: number, y: number): number {
return x + y
}
let result = fn1(1,2);
函數表達式
// 函數表達式
let fn2 = function(x: number, y:number): number {
return x+y;
}
完整函數類型寫法
// 完整的函數類型寫法
let fn2: (x:number, y:number) => number = function(x: number, y:number): number {
return x+y;
}
// 根據類型推斷可以簡寫
let fn2: (x: number, y:number) => number = function(x,y) {
return x+y;
}
可選參數寫法
// 可選參數,沒有返回值就使用viod關鍵字代替,可選參數使用?標識
function fn3(x: number, y?: number) :void {};
console.log(fn3(1));
參數默認值寫法
// 參數默認值
function fn3(x: number, y = 1): void {
console.log(y);
}
console.log(fn3(0));
剩余參數寫法
// 剩余參數
function createName(firstName: string, ...args: string[]){
return firstName + " " + args.join(" ");
}
let res = createName("Alex", "Bob", "Simth");
console.log(res);
函數重載
允許我們在TS中給函數傳遞多種對應類型的參數,前提是你已經定義好了參數的類型。
// function fn(x, y) {
// return x + y;
// }
// 如果在js中,我們是可以這么寫的,因為沒有類型檢查,但是在TS中使用類型檢查后推薦使用函數重載方式實現
// console.log(fn(1, 2)); // 3
// console.log(fn('a', 'b')); // ab
// 函數重載,允許我們在TS中給函數傳遞多種對應類型的參數
function fn(x: number, y: number): number;
function fn(x: string, y: string): string;
function fn(x: any, y: any): any {
return x + y;
}
console.log(fn(1,2)); // 3
console.log(fn('alex', 'zhang')); // alexzhang
// console.log(fn('haha', 123)); // 直接報錯
this
注意:在TS中能不使用any類型就不使用,因為使用any類型TS就不會對其做類型檢測了,就沒有任何意義了。
因為普通函數中的 this 具有執行期綁定的特性,所以在 ts 中的this 在有的時候會指向隱式的指向類型 - any(並不是所有,比如事件函數)。
我們可以通過 --noImplicitThis 選項來解決 this 隱式 any 類型的錯誤。
我們可以在函數參數中提供一個顯示的 this 參數,this 參數是一個假的參數,它出現在參數列表的最前面。
/**
* ts中默認情況下函數中的this默認指向 : any
*/
let obj = {
a: 10,
fn() {
// 因為默認情況下,this是any類型,any類型ts不能提示有任何屬性方法
// let document:any;
// any的值,ts不能提示或者進行類型屬性檢測
// console.log(this.b);
// 使用noImplicitThis選項可以取消默認this的any來這個設置
// this.a
}
}
// obj.fn();
// ts會自動推導事件函數中的this
// document.onclick = function() {
// this
// }
let obj1 = {
a: 1,
fn(this: Element|Document) { // 在ts中函數的第一個this參數是用來設置this類型約束的
// 這個this是一個假參數,運行過程中是不存在,是給ts檢測使用的
// console.log(this);
}
};
document.onclick = obj1.fn;
document.body.onclick = obj1.fn;
ts會自動推導事件函數中的this!!!
注意:TS是根據類型來做代碼檢測。
類
與 ES2015 中的 class 類似,同時新增了很多實用特性,與 ES2015 不同,TS 中的成員屬性可以提取到構造函數以外進行定義。
修飾符
通過修飾符可以對類中成員屬性與成員方法進行訪問控制,public、protected、private、readonly。
參數屬性:我們可以在參數中使用修飾符,它可以同時定義並初始化一個成員屬性。
class Person {
/**
* ts中的類,成員屬性必須要聲明后使用
* ts中的類的成員屬性不是在構造函數中聲明的,是在class內,方法外聲明
* public
* 公開的,所有的地方都能訪問,屬性和方法默認是public
* protected
* 受保護的,在類的內部和他的子類中才能訪問
* private
* 私有的,只能在該對象(類)的內部才可以訪問
*/
public username: string = '';
// private username: string = '';
// protected username: string = '';
constructor(name: string) {
this.username = name;
}
}
class Student extends Person {
say() {
console.log(`${this.username}:哈哈哈`);
}
}
let p1: Person = new Person('alex');
p1.username = 'john'
console.log(p1.username);
存取器
TS 支持 getters/setters 來截取對對象成員的訪問。
第一個需求:我們不希望年齡被修改。
解決方法:直接使用private關鍵字定義age成員屬性即可,因為private定義的屬性不能在外部訪問,只能在類的內部訪問。
此時,需求變動,我們允許用戶修改年齡,但是要在合理的訪問內修改,不能隨意修改,比如只能在0到150歲之間。
解決方法:在Person內中定義getAge和setAge方法來實現,getAge方法中直接返回類中的age屬性,setAge方法接收外部傳遞進來的參數,然后修改age屬性的值達到修改的效果,哦,別忘了加一個判斷,判斷通過才修改。
class Person {
username: string = 'alexander';
private _age: number = 21;
getAge(): number {
return this._age;
}
setAge(age: number): void {
if (age > 0 && age < 150) {
this._age = age;
}else {
console.log("[-]驗證未通過!");
}
}
}
let p1: Person = new Person();
// 需求:允許在外部獲取和修改age的值,但是不希望被修改成非法值,比如1000歲
console.log(p1);
console.log(p1.getAge());
console.log(p1.setAge(88));
console.log(p1.getAge());
使用TS的存取器來實現
class Person {
username: string = 'alexander';
private _age: number = 21;
// getAge(): number {
// return this._age;
// }
// setAge(age: number): void {
// if (age > 0 && age < 150) {
// this._age = age;
// }else {
// console.log("[-]驗證未通過!");
// }
// }
// 存取器,這個age並不會作為方法,而是作為屬性去訪問,類似Vue中的computed計算屬性
get age(): number {
return this._age;
}
set age(age: number) {
if (age > 0 && age < 150) {
this._age = age;
}
}
}
let p1: Person = new Person();
// 需求:允許在外部獲取和修改age的值,但是不希望被修改成非法值,比如1000歲
console.log(p1.age) // 21
p1.age = 121; // set age 121
console.log(p1.age); // 121
p1.age = 123; //set age error
console.log(p1.age); // 123
TS官方的demo
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);
}
注意下面幾點:
-
使用存取器的成員需要被
private修飾。 -
編譯目標為 ES5+。
-
只有 get 的存取器自動被推斷為 readonly只讀屬性。
沒看官方文檔之前,我還傻傻地使用p1.age(121)的方式嘗試修改_age屬性的值,始終不成功,看了文檔之后發現我的寫法是錯的,應該用=號賦值的方式來寫,總結一下,與其質疑框架、工具、庫版本問題,坑多難用,不如多看官方文檔,文檔看不好,Bug少不了!:)
靜態成員
類的一般成員屬性和方法都屬於實例對象的,也就是原型鏈上的,靜態成員屬於類(也就是構造函數)的,靜態成員不需要實例化對象,直接通過類即可調用。
通過下面的demo徹底理解
// 單例模式demo
// class Mysql {
// // 成員屬性聲明, 默認public
// host: string;
// port: number;
// username: string;
// password: string;
// dbname: string;
// constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
// this.host = host;
// this.port = port;
// this.username = username;
// this.password = password;
// this.dbname = dbname;
// }
// // 類方法
// query(){console.log("query data...")}
// insert(){console.log("insert data...")}
// update(){console.log("update data...")}
// }
// /**
// * 創建一個Mysql對象,通過這個對象來操作數據庫
// * 如果我們不加以限制的話,這個Mysql是可以new出來多個對象的
// * 每一個Mysql都會占用資源(內存)
// *
// * 為了解決這個問題,我們需要對創建Mysql連接做限制,如果存在則直接使用已有的連接,不存在
// * 則創建。
// */
// let db = new Mysql();
// db.query();
// db.insert();
// let db1 = new Mysql();
// db1.query();
// db1.insert();
/**
* 通過某種方式控制系統同時只有一個Mysql的對象在工作
*/
class Mysql {
// 靜態屬性,不需要通過new出來的對象,直接是通過Mysql類來訪問
public static instance;
host: string;
port: number;
username: string;
password: string;
dbname: string;
private constructor(host = '127.0.0.1', port = 3306, username = 'root', password = '', dbname = '') {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.dbname = dbname;
}
public static getInstance() {
if (!Mysql.instance) {
Mysql.instance = new Mysql();
}
return Mysql.instance;
}
query() { console.log("query data...") }
insert() { console.log("insert data...") }
update() { console.log("update data...") }
}
// let db = new Mysql();
console.log(Mysql.instance);
let db = Mysql.getInstance();
db.query();
console.log(Mysql.instance);
db.insert();
db.update();
繼承
class Person {
// 在構造函數的參數中如果直接使用public等修飾符,則等同於同時創建了該屬性
constructor(public username: string, public age:number) {
this.username = username;
this.age = age;
}
}
class Student extends Person {
/**
* 如果子類沒有重寫構造函數,則直接使用父類的
* 如果子類重寫了構造函數,則需要手動調用父類構造函數
* super:關鍵字,表示父類
*/
constructor(username: string, age:number, public gender: string) {
super(username, age); // 執行父類構造函數
this.gender = gender
}
}
let s1 = new Student('alexander', 21, '男');
console.log(s1);
抽象類
類是對具有相同特性的對象的抽象,抽象類是對具有相同特性的類的抽象,當派生類(子類)具有的相同的方法但有不同實現的時候,可以定義抽象類並定義抽象方法。
抽象方法只定義結構不定義實現,擁有抽象方法的類必須是抽象類,但是抽象類不一定擁有抽象方法,抽象類中也可以包含有具體細節的方法,abstract 關鍵字可以與 修飾符一起使用,繼承了抽象類的子類必須實現了所有抽象方法才能被實例化,否則該子類也必須聲明為抽象的。
第一次接觸這個概念感覺有億點點抽象,還是直接看代碼。
abstract class Person { // 抽象類是不能實例化的
username: string;
constructor(username: string) {
this.username = username;
}
say() {
console.log("哈哈哈哈哈");
}
/*
雖然子類都會有這樣的特性,學習,但是子類的學習具體過程不一樣,所以在父類確定不了study方法
的具體實現,父類只能有抽象的約定,接收什么參數,返回什么內容。
如果一個類中有抽象的方法了,那么這個類2也必須是抽象的。
*/
abstract study():void // 抽象方法是沒有具體代碼的
}
class Student extends Person {
study() {
console.log("學生有學生的學習方法 - 需要老師教授")
}
}
class Teacher extends Person {
study() {
console.log("老師的學習方法 - 自學");
}
}
// 如果一個類繼承了抽象的父類,就必須實現所有抽象方法,否則這個子類也必須是一個抽象類
abstract class P extends Person {}
let s1 = new Student('alex');
console.log(s1.say());
console.log(s1.study());
看完代碼就理解了上面的話了。
Part3內容
接口(interface)
Hello,Interface
接口為我們提供一種方式來定義某種結構,ts按照這種結構來檢測數據。
下面看一個基礎的例子。
/**
* interface
* 為我們提供一種方式來定義某種結構,ts按照這種結構來檢測數據
*
* 寫法:
* interface 接口名稱 {
* // ...接口規則
* }
*
*
* 接口中定義的規則只有抽象描述,不能有具體的值或實現代碼
*
* 對象抽象 => 類 (把對象相似的部分提取出來通過這個類去描述對象)
* 類抽象 => 抽象類 (如果一個類中有一個抽象方法沒有實現,那么這個類就是抽象類)
* 抽象類 => 接口 (如果一個抽象類中的所有成員都是抽象的,這個類就是接口)
*/
// 定義一個名為Options接口,可以把接口看成是一個對象,但是不完全是,有些細節不一樣。
interface Options {
// width: number = 1, // 接口中的代碼不能有值
width: number,
height: number
}
function fn(opts: Options) {
console.log(opts);
}
// fn(); // 報錯,沒有傳入參數
// fn({}); // 傳入的參數類型不對,ts會按照接口中定義的數據去檢測
// fn({width: 300}); // 缺少height屬性
// 細節:類型檢測只檢測必須的屬性是否存在,不會按照順序進行檢測,是無序的
fn({ width: 200, height: 150 }); // 正確
ts中的interface接口是用來定義規則的,這個規則是給ts用來做數據檢測的,上面的fn函數就是用了Options接口的規則來做數據檢驗,規則中定義了number類型的width屬性和number類型的height屬性,給fn函數傳參的時候就得按照這種規則傳,否則就報錯。
總結:
- ts中的接口是用來做類型檢驗的,必須嚴格按照接口中定義的規則來實現
- ts的類型檢測只檢測必須的屬性是否存在,不會按照順序進行檢測,是無序的
可選參數和只讀屬性
/**
* 如果規則中有些是可選的,那么通過 ? 標識。
* 只讀屬性通過 readonly 關鍵字標識
*/
interface Options {
width: number,
height: number,
color?: string,
readonly opcity: number
}
function fn(params: Options) {
console.log(params);
// params.opcity = 1.2 // 報錯,只讀屬性不能重新賦值
}
fn({
width: 200,
height: 250,
opcity: 0.5
})
總結:
- 可選參數通過
?標識 - 只讀屬性通過
readonly關鍵詞標識
檢測約束
/*
如果我們希望檢測不要這么復雜
-如果我們希望某些時候,只要包含其中一些規則即可
- 通過可選參數 ? 方式實現
- 通過 as 斷言 可以傳少,不能傳多
- 通過變量轉換 可以傳多,不能傳少
*/
interface Options {
width: number,
height: number,
color: string
}
function fn(params: Options) {
console.log(params);
}
// fn({
// width: 200,
// height: 300
// } as Options); // 明確告訴ts我傳入的就是Options,讓ts繞開檢測
// 先賦值給一個變量,也可以繞開規則檢測,原因是ts沒有對obj這個變量做類型檢測,但是這種方式只能傳多不能傳少
let obj = {
height: 200,
width: 100,
color: 'red',
a: 1,
b: 2,
c: 3
}
fn(obj);
索引簽名
/*
希望規則是:一組由數字進行key命名的對象,比如下標為0,1,2,3,4這樣的
我們可以使用索引簽名
為數據定義一組具有某種特性的key的數據
索引key的類型只能是 number和string兩種
*/
// 需求類似下面這種結構
// interface Options {
// 0: string,
// 1: string,
// 2: string
// }
// 定義了一組規則,key是number類型,使用中括號包裹[變量名: 類型], value是any任意類型
interface Options {
// key是number,value是any類型的
// [attr: number]: any,
// key是string的話,會同時支持string和number類型
[attr: string]: any,
length: number
}
function fn(params: Options) {
console.log(params)
}
// 這種結構類似調用document.querySelectorAll('div')返回的NodeList結構
fn({
0: 100,
a: 'haha',
length: 1
})
函數類型接口
通過接口的形式來定義函數。
一個簡單的函數類型接口定義。
/*
這個接口描述的是一個包含fn,並且值的類型為函數的結構體,並不是描述函數結構;
注意描述的是一個包含函數的對象結構
*/
interface Options {
fn: Function
}
let o: Options = {
fn: function () {console.log("I Am Function...")}
}
o.fn(); // I Am Function...
注意:我們不能把一個函數隨便賦值給事件,因為事件回調函數的參數類型是Event,這是JS規定的。
// 完整描述函數結構寫法
// let fn: (x: number, y: number) => number = function (x: number, y: number): number {
// return x + y
// }
/*
定義一個事件函數,那么這個函數必須得有一定的規則;
我們不能隨隨便便的把一個函數賦值給事件
*/
// document.onclick = fn; // 報錯,因為fn函數定義的參數類型是number,而事件觸發后的事件對象是Event類型
function fn(x: Event) {
}
document.onclick = fn; // 正確寫法
再來看一個函數類型接口的例子。
// 我們可以使用 interface 來約定函數的結構
// 定義的是函數類型接口,定義了一個x,y參數和返回值都為number類型的函數,根據這種規則進行檢測
interface IFn {
(x: number, y: number): number
}
let fn: IFn = function(x: number, y: number): number {return x + y};
// 定義了一個接收一個MouseEvent類型參數的函數結構
// 其實函數接口就是定義一種函數規則,然后后面復用這種規則
interface MouseEventCallBack {
(e: MouseEvent): any
}
let fn: MouseEventCallBack = function(a: MouseEvent){
}
document.onclick = fn
定義一個名為MouseEventCallBack的接口,接收的參數名為e,類型是MouseEvent類型,返回值是any。
fn函數按照MouseEventCallBack接口定義的規則做檢測,函數中參數a是一個MouseEvent類型,因此ts檢測通過,可以將fn函數賦值給事件。
再來舉一個例子。
/*
定義一個ResponseCallBack函數接口
函數的第一個參數名為res,Response類型,返回值是any類型的
*/
interface ResponseCallBack {
(res: Response): any
}
function todo(callback: ResponseCallBack) {
callback(new Response)
}
todo(function(res: Response){
})
函數todo接收一個callback參數作為回調函數並且按照ResponseCallBack函數接口做規則檢測,檢測的規則是接收一個名為res的參數,類型是Response類型,返回值是any類型,調用todo函數,傳入一個res參數名,類型為Response,然后調用callback並傳入一個Response對象。
部分代碼需要拿到瀏覽器環境跑,直接用node跑可能會報錯,例如這個Response。
再來看一個fetch的例子。
// fetch返回的是一個Promise對象,then方法成功回調函數的參數是一個Response類型對象
fetch('url').then( (a: string) => {
a.indexOf(''); // ts會檢測到a不是一個string類型的,根本沒有indexOf方法,這時就會報錯
})
fetch('url').then((a: Response) => {
// a.indexOf(''); // Response類型沒有這個indexOf方法,這里就會報錯
return a.json(); // Response對象是有json方法的,所以檢測通過,可以參考MDN文檔的Response對象
})
不要嘗試去欺騙ts編譯器,沒有意義,ts並不會按照你寫的a:string就將a按照string類型進行檢測,因為then方法的回調函數參數就是一個Response對象,這是JS的規則。
再來看一個ajax的例子。
// 再看一個ajax的例子
interface AjaxData {
code: number,
data: any
}
interface AjaxCallBack {
(res: AjaxData): any
}
// ajax函數的參數是一個callback回調函數,類型是AjaxCallBack,AjaxCallBack的參數類型是AjaxData類型
function ajax(callback: AjaxCallBack){
callback({
code: 1,
data: [
{goods_name: 'IKBC C87紅軸', goods_id: 1, goods_price: 299.99, goods_color: 'Black'},
{goods_name: 'IKBC C87青軸', goods_id: 2, goods_price: 289.99, goods_color: 'Green'},
{goods_name: 'IKBC C87黑軸', goods_id: 3, goods_price: 279.99, goods_color: 'White'},
],
// message: '123' // AjaxData沒有定義message屬性所以為報錯
})
// 可以通過之前學習到的變量賦值的方式繞過檢測
// let obj = {
// code: 1,
// data: [],
// message: '123'
// }
// callback(obj)
}
ajax(function(x: AjaxData){
console.log(x);
})
// 總結:函數接口其實就是定義某種函數結構,接收的參數是什么類型,返回值是什么類型,做嚴格約束
// typescript倒逼我們學習了更多javascript語言本身的東西,例如各種規則
總結:函數接口其實就是定義某種函數結構,接收的參數是什么類型,返回值是什么類型,做嚴格約束。
第一次看這個的時候一臉懵逼=>萬臉懵逼=>遞歸懵逼,只能怪自己太蠢,后面靜下心來反復看,反復理解,反復寫就明白了,slow is fast。
類類型接口
/**
* 類接口
* 使用接口讓某個類去符合某種契約
*
* 類可以通過 implements 關鍵字去實現某個接口
* - implements 某個接口的類必須實現這個接口中確定所有的內容
* - 一個類只能有一個父類,但是可以implements多個接口,多個接口使用逗號分隔
*/
interface ISuper {
fly(): void;
}
class Man {
constructor(public name: string) {
}
}
class SuperMan extends Man implements ISuper {
fly() {
console.log('起飛');
}
}
class Cat {
}
class SuperCat extends Cat implements ISuper {
fly() {
console.log('起飛');
}
}
let kimoo = new SuperMan('Kimoo');
// kimoo
封裝http案例
js版本
function http(params) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(params.method, params.url, params.isAsync);
xhr.onload = function () {
resolve(JSON.parse(xhr.responseText));
}
xhr.onerror = function () {
reject({
code: xhr.response.code,
message: '出錯了!'
})
}
xhr.send();
})
}
// 期待的使用方式
// http('url').then( data => {} )
http({
method: 'get',
url: 'http://www.baidu.com/',
isAsync: true
})
/*
問題:如果將method改成methods,因為沒有類型檢測,你很難在代碼里面看出來
必須在運行之后才能看出來。
這個時候如果使用ts來實現就會好很多。
*/
ts版本
interface HttpOptions {
method: string,
url: string,
isAsync: true
}
interface HttpResponseData {
code: number,
data: any
}
function http(options: HttpOptions) {
// 默認值處理
let params: HttpOptions = Object.assign({
method: 'get',
url: '',
isAsync: true
}, options)
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(params.method, params.url, params.isAsync);
xhr.onload = function () {
let data: HttpResponseData = JSON.parse(xhr.responseText);
resolve(data);
}
xhr.onerror = function () {
reject({
code: 0,
data: []
})
}
xhr.send();
})
}
// 期待的使用方式
// http('url').then( data => {} )
http({
method: 'get',
// methods: 'get', // 報錯,因為HttpOptions接口中沒有定義methods屬性,ts會幫助我們在程序運行之前檢測代碼是否錯誤
url: 'https://www.baidu.com/',
isAsync: true
}).then( res => {
console.log(res);
})
總結
安裝TS:npm install -g typescript
安裝ts-node編譯器:npm install -g ts-node
tsconfig.json作用是什么?
是用來對typescript編譯器進行配置的,比如配置編譯模式、模塊化系統、輸出目錄、編譯文件目錄、包含文件的層級設置、空值檢查、取消this默認指向any等等。。。
類型系統,常用有哪些類型?
格式:let a: 類型 = 123
- string
- number
- boolean
- array
- tuple(元組)
- enum
聯合類型:可以使用定義好的類型中任意一種類型進行賦值
類型推導:typescript編譯器會根據初始化變量或上下文來做類型推導,例如一個函數的2個參數都是定義為number類型的,那么返回值根據類型推導也是number類型,此時可以省略不寫。
函數創建方式:
- 函數聲明
function fn1(x: number, y: number): number {return x+y}; - 函數表達式
let fn2 = function(x: number, y: number):number {return x+y};
完整函數寫法:
let fn2: (x:number, y:number) => number = function(x:number, y:number):number{return x+y};
根據類型推斷簡寫:
let fn2: (x:number, y:number) => number = function(x,y){return x+y};
可選參數寫法:使用?標識,如果沒有返回值使用void代替
function fn3(x: number, y?:number):void{console.log(y)}
參數默認值寫法:和javascript一樣使用=號
function fn3(x: number, y=1): void {console.log(y)}
剩余參數寫法:
function fn3(x: number, ...args: string[]){ return x + " " + args.join(" "); }
函數重載:允許我們在TS中給函數傳遞多種對應類型的參數,前提是你已經定義好了參數的類型。
function fn(x: number, y: number);
function fn(x: string, y: string);
function fn(x: any, y: any): any {return x + y}
TS函數中的this:
在ts中,函數默認的this指向any(並不是所有,比如事件函數除外),這會導致ts的類型系統不會檢測this,因為ts不會檢測any類型,所以我們需要配置noImplicitThis:true來解決this默認指向any的問題。
在ts中函數的第一個this參數是用來設置this類型約束的,約定this指向的是什么類型。
類
類是對有相同特性對象的抽象。
修飾符:
作用是為了對成員屬性和方法進行訪問控制
- public:ts中類的默認修飾符,允許類中的屬性和方法公開訪問
- private:私有修飾符,只允許屬性在類的內部進行訪問
- protected:受保護修飾符,只允許屬性在類的內部和它的子類中訪問
- readonly:將屬性設置為只讀的, 只讀屬性必須在聲明時或構造函數里被初始化
存取器:
截取對對象成員的訪問,get/set,注意使用get/set定義的方法在使用的時候不需要加括號,當作屬性使用即可,參考修改年齡代碼。
靜態成員:
不需要通過實例化的對象,直接通過類進行調用,適用於同時只能有一個對象在工作的情況,參考單例模式的Mysql連接代碼。
繼承:
類似ES6,使用extends關鍵字進行繼承,如果子類沒有重寫構造函數,則直接使用父類的,如果重寫了構造函數,則需要手動調用父類構造函數,使用super()關鍵字調用。
抽象類:
抽象類是對具有相同特性的類的抽象,當子類具有的相同的方法但有不同實現的時候,可以定義抽象類並定義抽象方法;
參考學生學習方法和老師學習方法的代碼。
