本文是《約束即類型、TypeScript 編程內參》系列第一篇:約束即類型,主要記述 TypeScript 的基本使用和語法。前端
PS: 本文語境下的「約束」指的是「類型對值的約束」java
TS 你們都據說或者使用過,是 Angular 的官方語言,提供了靜態類型檢查,是 JavaScript 這門語言的超集,也就是說:node
TS = JS + 靜態類型檢查程序員
TS 今年開始火了,愈來愈多的 js 項目開始用 ts 來實現,所以有了一句廣爲流傳的名言(捏他)typescript
任何用 js 寫的項目終將用 ts 重構npm
那麼,你瞭解 ts 嗎?類型本質上是對變量的約束,理解類型,首先要理解的是變量的值,而後 ......編程
本文是本系列的第一篇約束即類型,面向的是「有必定 JS 開發經驗的學習者」 ,推薦前端/node工程師學習,建議跟隨本文的代碼邊寫邊看,包教不教會。json
經過如下方式初始化一個 ts 項目並編譯運行:promise
$ npm i -g typescript # 安裝 ts
$ mkdir my-ts-learn # 搭建 playground
$ cd my-ts-learn # 進入目錄
$ tsc --init # 初始化一個 ts 工程
$ echo "console.log('hello, ts');" >> h.ts # 建立代碼
$ tsc # 編譯
$ node h # 運行
複製代碼
💡💡💡bash
關於 tsconfig.json 咱們會在本系列的其餘篇目介紹,敬請期待 對於初學者,暫時先使用默認的配置就好
「any 即無約束」它表明着任意,以下所示:
let a: any = 123;
a = {};
a = () => {};
a = '不會報錯';
複製代碼
電視裏常說 AnyScrtipt 指的就是無理由的在 TS 裏大量使用 any;越是使用 any,則 ts 越像 js,不建議這樣作,這樣會失去使用 ts 的意義。
💡💡💡
類型自己就是對程序的證實
const num: number = 123;
const str: string = 'eczn';
複製代碼
不少狀況下,咱們並不須要顯式地指明類型是什麼,ts 會幫咱們自動地進行「類型推斷」,比方說下面這樣寫的話,ts 會自動推斷出 val 的類型是 string:
let val = '123';
val = 123; // 不行,會報錯
複製代碼
💡💡💡
好的 ts 代碼老是這樣的:大部分變量的類型是 ts 自動推斷出來的,而不是程序員處處給變量加類型(這樣就成 java 了)
通常狀況下,咱們能夠利用 interface
和 type
聲明來創造對 JS 對象的「約束」。
interface Person1 {
name: string;
}
type Person2 = {
name: string;
}
const p1: Person1 = { name: '001 號居民' };
const p2: Person2 = { name: '002 號居民' };
複製代碼
可是有一點要清楚,TS 的類型學名上叫作 結構化類型 Structral Type
,跟其餘語言裏的 具名類型 Naming Type
不太同樣,譬如說:
interface Person3 = {
name: string;
}
const p1: Person1 = { name: '001 號居民' };
const p3: Person3 = p1;
// 在 naming type 的語言中,這樣會報錯
// 但在 ts 這種 structral type 的語言下,這樣不會報錯
複製代碼
簡而言之,structral type 進行類型檢查的時候比較的是兩類型的結構,若是同樣說明類型同樣;而 naming type 僅是比較類型的標識符是否是同樣,不同則認爲類型不合。
因此我更傾向於認爲,structral type 的做用,其實對值的一種約束。
💡💡💡
type 和 interface 二者在不少狀況下是能夠等價互相轉換的,但實際上二者是有很大不一樣的,文章系列後文會描述
函數的類型由這三者描述:i 入參、ii 返回值、iii 上下文:
interface Person { name: string; };
// 需注意,這裏的 this 通過 ts 編譯後會消失
// 同窗們能夠自行編譯體會個中奧義
function sayName(this: Person, suffix: string) {
return this.name + ' ' + suffix;
}
const 無名先生 = { sayName };
const 阿J = { name: 'j', sayName };
// 報錯,無名先生無名,不知足 this 上下文類型約束
無名先生.sayName('先生');
// 這裏 阿 J 有 name 屬性,知足 this 約束
阿J.sayName('先生');
// 注意,箭頭函數在語言定義上是沒有上下文的
// 所以下面這個會報錯
const test = (this: any, x: string): string => x;
複製代碼
固然,在類裏面聲明的類型,其 this 已經默認爲其自己了:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
sayName(suffix: string) {
// 這裏 ts 能正確識別 this 指的是 Person 類
// 不會報錯
return this.name + ' ' + suffix;
}
}
複製代碼
💡💡💡
不少時候咱們沒必要吧函數的上下文也定義出來,通常都是這樣定義函數的:
const func = (a: X): Y = { /* function body */ }
對於值來講,字面量類型是比基本類型更窄的約束,好比:
function sayHello(suffix: '先生') {
return 'hello,' + suffix;
}
sayHello('先生'); // 知足字面量類型,不會報錯
sayHello('女士'); // 不知足,類型不合
複製代碼
咱們熟知的 number 類型也有對應的字面量類型,讀者能夠自行寫 demo 驗證推敲。
ts 拓展了 js 語法裏面的 typeof
,使其能夠在 ts 進行類型聲明的時候獲取某個變量的類型:
let num = 123;
type MyNumber = typeof num;
// MyNumber 指的是就是 number 類型
複製代碼
ts 不少狀況下是不用聲明類型的,ts 會自動推斷的,好比下面 UP_POSITION
的類型 ts 會自動推斷爲類型 string
:
let UP_POSITION = '向上';
複製代碼
可是下面這種狀況呢?
const UP_POSITION_2 = '向上';
複製代碼
這種狀況下咱們獲得的類型是一個字面量類型 '向上'
,以下所示
形成這種緣由的結果是 const 聲明的變量不會再變了,所以出來的是字面量類型,而 let 聲明的類型是不肯定的,所以用的是 string
那若是,我必定要在 let 的狀況下推斷某變量的類型爲字面量呢?能夠這樣:
let UP_POSITION = '向上' as const;
type Test = typeof UP_POSITION;
// Test 是 '向上'
複製代碼
as const 會告訴 ts 請勿擴大範圍。
💡💡💡
仔細想想下面三行代碼以及背後的運做原理 🤔
let a = 123; let t = typeof a; // "number" type T = typeof a; // number 複製代碼
泛型的意義在於,他聲明瞭類型的上下文環境,使類型能夠做爲參數進行傳遞,好比我想實現一個數學上的常函數 x => x,ts 實現以下(須要用到泛型):
這裏的 ts 聲明描述了:
當具體 ts 去推斷 val 的類型的時候,就能夠發現 val 是 id('123') 這時候 T = '123' 所以 val 的類型就是 '123'。
泛型無處不在,它是類型的拓展,咱們通常利用泛型去定義 可拓展的數據結構/接口/類型, 如 js 一些原生類裏面就有泛型的影子:
// 求和 arr 並結果將其以 promise 的形式包裹返回
function sum(arr: Array<number>): Promise<number> {
const sum = arr.reduce((a, b) => a + b);
return Promise.resolve(sum);
}
sum([1, 2, 3]).then(console.log);
// => 6
複製代碼
💡💡💡
泛型裏的泛是寬泛的泛,而不是範式的範。
最後一例:利用泛型實現鏈表:
// 鏈表, 這裏聲明瞭泛型 T
class CommomList<T> {
value: T;
// 這裏的意思幾乎等價於下面這種寫法,用於聲明可能不存在的字段:
// next: CommomList<T> | undefined;
next?: CommomList<T>;
// 構造函數
constructor(value: T, next?: CommomList<T>) {
this.value = value;
this.next = next;
}
// 設置 next
setNext(next: CommomList<T>) { // next 是下一個節點
this.next = next;
return next;
}
// 鏈表生長
grow(value: T) { // value 下一個節點的值
const t = new CommomList(value);
this.setNext(t);
return t;
}
// 遞歸地打印本身
toString(): string {
return this.next ?
JSON.stringify(this.value) + ' -> ' + this.next.toString() :
JSON.stringify(this.value) + ' -> null';
}
}
const _1 = new CommomList(1);
_1.grow(2).grow(3).grow(4);
// _1 是頭
console.log(_1.toString());
複製代碼
本文說明了 TS 的基本概念和使用方式,如下是總結 CheckList:
本文的下一篇是「構造類型抽象、TypeScript 編程內參(二)」,敬請期待