本文為翻譯文章鳞尔,原文鏈接見文末
作為一個JavaScript開發(fā)者,你可以編寫一整天編寫也不會遇到任何靜態(tài)類型檢查得問題。那么為什么要自找麻煩得去學習它呢智玻?
然而學習靜態(tài)類型并不僅僅是一個思維拓展的訓練。如果你愿意花點時間來學習一些靜態(tài)類型的優(yōu)勢芙代、劣勢以及使用的案例吊奢,那將會極大的幫助你進行編碼。
怎么樣纹烹,有意思吧页滚?要是你感興趣的話,那接下來四個部分將會向你詳細解釋铺呵。
一裹驰、定義
理解靜態(tài)類型最快捷的方式就是它和動態(tài)類型進行對比。
A language with static types is referred to as a statically-typed language. On the other hand, a language with dynamic types is referred to as a dynamically-typed language.
靜態(tài)類型語言和動態(tài)類型語言得核心區(qū)別在于片挂,靜態(tài)類型語言(statically-typed languages)會在編譯時(compile time)進行類型檢查幻林,而動態(tài)語言(dynamically-typed)則是在運行時進行類型檢查(runtime)。
這里又多了一個概念:什么是“類型檢查”(type-checking)音念?
為了解釋這個概念沪饺,我們可以將Java于JavaScript的類型對比看一下。
這里的類型(Types)指的是一個數(shù)據(jù)被定義的類型闷愤。
舉個例子整葡,在Java中如果你定義一個boolean
值:
boolean result = true;
這個變量就有了一個正確的類型,因為boolean
類型的聲明和這個變量給定的值是相符的讥脐。
在另一方面遭居,如果你嘗試這么來聲明這個變量:
boolean result = 123;
由于變量result
有一個錯誤的類型,因此會編譯失敗攘烛。這里我們明確地聲明了result
是一個boolean
類型魏滚,但是它卻用整型值123
賦給了它。
JavaScript和其他一些動態(tài)類型語言有著不同的處理方法坟漱,他們允許上下文環(huán)境來確定數(shù)據(jù)需要被定義為什么類型:
var result = true;
長話短說鼠次,靜態(tài)類型語言需要你在能夠使用這個變量之前定義它的類型。而動態(tài)類型語言則不同。JavaScript中變量類型是被“隱去”的腥寇,而Java中則是顯式聲明的成翩。
類型檢查將會確保并且強制使你的變量類型(constant, boolean, number, variable, array, object)和你已經(jīng)定義和預期的內(nèi)容相符。例如:你已經(jīng)確定“這個方法總是會返回一個string型”赦役。當程序運行的時候麻敌,你可以很安全地假設(shè)它會返回一個string型。
當出現(xiàn)一個類型錯誤時掂摔,靜態(tài)類型檢查和動態(tài)類型檢查的差異就凸顯出來了术羔。在靜態(tài)類型語言中,類型檢查發(fā)生在編譯階段乙漓。在動態(tài)類型語言中级历,只有在程序運行了一次的時候錯誤才會被發(fā)現(xiàn),也就是在運行時叭披。
這就意味著寥殖,對于寫動態(tài)類型語言的程序員而言,即使代碼中包含了會在運行時阻止腳本正常運行的錯誤類型涩蜘,這段代碼也可以通過編譯嚼贡。
在另一方面,如果一個寫靜態(tài)語言的程序員寫了一段包含了類型錯誤的代碼同诫,那么除非修復這個錯誤粤策,否則會一直編譯失敗。
A new era of JavaScript
因為JavaScript是一種動態(tài)類型語言误窖,因此你可以定義各種變量掐场、方法、對象而不需要聲明它的類型贩猎。
var myString = "my string";
var myNumber = 777;
var myObject = {
name: "Preethi",
age: 26,
};
function add(x, y) {
return x + y;
}
這非常方便,但有時確不是那么理想萍膛。這也就是為什么想Flow和TypeScript這樣的工具最近開始走入人們的視野吭服,帶給JavaScript開發(fā)者使用靜態(tài)類型的選擇。
Flow時Facebook開發(fā)和發(fā)布的一個開源的靜態(tài)類型檢查庫蝗罗,它允許你逐漸地向你的JavaScript代碼中添加類型艇棕。
TypeScript是一個會編譯為JavaScript的超集(盡管它看起來幾乎像一種新的靜態(tài)類型語言),這意味著串塑,它使用起來會感覺和JavaScript很像沼琉,并不難上手。
不論是使用上面哪種工具桩匪,當你想要使用類型時打瘪,你會明確告訴工具哪個(些)文件需要類型檢查。對于TypeScript,你需要將.js
文件拓展名改為.ts
闺骚。對于Flow彩扔,你需要在文件的頂部引入一段標注@flow
。
一旦你聲明了想要對某一個文件進行類型檢查僻爽,你需要使用他們各自的語法去定義類型虫碉。這兩個工具的一個區(qū)別在于,F(xiàn)low是一個類型“檢查器”而不是一個編譯器胸梆。TypeScript則是一個編譯器敦捧。
我相信類似Flow和TypeScript這樣的工具為JavaScript帶來了一個跨世代的轉(zhuǎn)變與提高。
下面我會從這四個部分來談一談“在javascript中進行靜態(tài)類型檢查”:
- 第一部分 Flow語法的快速入門
- 第二碰镜、三部分 (通過詳細的示例來闡述)靜態(tài)類型的優(yōu)缺點
- 第四部分 是否應該在JavaScript種使用靜態(tài)類型呢兢卵?
注意我選擇在例子中使用Flow而不是TypeScript是因為我比較熟悉它。你可以根據(jù)你自己的目的與場景選擇一個對你來說合適的工具洋措。TypeScript同樣非常不錯济蝉!
話不多說,開搞菠发!
第一部分王滤、Flow語法快速人們
為了要理解靜態(tài)類型的優(yōu)勢和劣勢,你首先需要通過使用Flow來對靜態(tài)類型的語法有個基礎(chǔ)的認識滓鸠。如果你以前從來沒用過靜態(tài)類型雁乡,那么你可以需要花點時間來熟悉一下這種語法。
讓我們先來看看如何在JavaScript基本類型(以及像數(shù)組糜俗、對象踱稍、函數(shù)等)上應用。
boolean
下面這段代碼描述了JavaScript中的boolean
值
var isFetching: boolean = false;
注意悠抹,當你想要定義一個類型時珠月,你需要用下面這種語法:

number
這里指的是IEEE 754下的浮點型。不像許多其他的程序語言楔敌,JavaScript并不會定義不同的數(shù)值類型(例如interger啤挎、short、long和float points)卵凑。而是所有的數(shù)值類型都會被存儲為雙精度浮點數(shù)庆聘。因此,你只需要一種數(shù)據(jù)類型來定義所有的數(shù)值變量勺卢。
備注:數(shù)值型number
包含了Infinity
和NaN
var luckyNumber: number = 10;
var notSoLuckyNumber: number = NaN;
string
下面是一個string
類型
var myName: string = 'Preethi';
null
下面是一個null
數(shù)據(jù)類型
var data: null = null;
void
在這里void描述的是JavaScript中undefined
類型
var data: void = undefined;
注意要將null
和undefined
區(qū)別對待伙判。如果你像下面這么寫:
var data: void = null;
/*------------------------FLOW ERROR------------------------*/
20: var data: void = null
^ null. This type is incompatible with
20: var data: void = null
^ undefined
由于undefined
和null
是不同的類型,而void
類型應該屬于undefined
類型黑忱,因此Flow會拋出一個錯誤宴抚。
Array
JavaScript中的數(shù)組類型勒魔。使用Array<T>
這樣的語法來定義一個數(shù)組,其中數(shù)組的元素類型為T
酱塔。
var messages: Array<string> = ['hello', 'world', '!'];
注意上面的代碼沥邻,用string
替換了T
,表示messages
是一個字符串數(shù)組羊娃。
Object
JavaScript中的對象類型唐全。有幾種不同的方式來為對象添加類型限制。
你可以添加類型來描述對象的格式:
var aboutMe: { name: string, age: number } = {
name: 'Preethi',
age: 26,
};
你可以用對象來作為Map蕊玷,并給鍵和值都設(shè)置類型:
var namesAndCities: { [name: string]: string } = {
Preethi: 'San Francisco',
Vivian: 'Palo Alto',
};
你也可以僅僅定義一個對象為Object
類型:
var someObject: Object = {};
someObject.name = {};
someObject.name.first = 'Preethi';
someObject.age = 26;
上面這段代碼使你可以不受限制得設(shè)置對象的鍵和值邮利,因此就類型檢查而言,這種方式并沒有增加太多的價值垃帅。
any
正如字面意思延届,它可以代表任何類型。any
類型一定程度上避免了類型檢查贸诚,因此如非必要方庭,盡量扁面使用這個類型。
var iCanBeAnything:any = 'LALA' + 2; // 'LALA2'
有一個場景下比較適用:當你使用了一個擴展了系統(tǒng)原型的外部類庫(類似Object.prototype
)酱固。
例如械念,你使用的類庫為Object.prototype
擴展了一個叫doSomething
的屬性。
Object.prototype.someProperty('something');
你的代碼很可能會報如下錯誤:
41: Object.prototype.someProperty('something')
^^^^^^ property `someProperty`. Property not found in
41: Object.prototype.someProperty('something')
^^^^^^^^^^^^ Object
為了避免著各種情況运悲,你可以使用any
類型:
(Object.prototype: any).someProperty('something'); // No errors!
Functions
給方法添加類型的最常見的用法是龄减,為該方法的參數(shù)和返回值添加類型檢查:
var calculateArea = (radius: number): number => {
return 3.14 * radius * radius
};
你甚至可以給async方法和生成器(generator)添加類型:
async function amountExceedsPurchaseLimit(
amount: number,
getPurchaseLimit: () => Promise<number>
): Promise<boolean> {
var limit = await getPurchaseLimit();
return limit > amount;
}
這里可以關(guān)注一下,這段代碼是如何將第二個參數(shù)getPurchaseLimit
聲明為一個返回Promise
對象的函數(shù)的班眯。同時希停,
amountExceedsPurchaseLimit
方法本身被聲明會返回一個Promise
對象。
類型別名(Type alias)
類型別名(Type alias)是我最喜歡的一種用法署隘。它允許你使用已有的類型(number,宠能、string等)來組合成一個新的類型:
type PaymentMethod = {
id: number,
name: string,
limit: number,
};
在上面這段代碼中,我創(chuàng)建了一個叫作PaymentMethod
的新類型磁餐,包含了number
和string
兩種類型棍潘。
可以這樣來使用PaymentMethod
類型:
var myPaypal: PaymentMethod = {
id: 123456,
name: 'Preethi Paypal',
limit: 10000,
};
通過給原始類型包裹一層新類型,你可以為它們創(chuàng)建一個新的類型別名崖媚。例如,創(chuàng)建Name
和Email
這兩個類型別名:
type Name = string;
type Email = string;
var myName: Name = 'Preethi';
var myEmail: Email = 'iam.preethi.k@gmail.com';
這么做的話恤浪,可以清楚的表明畅哑,Name
和Email
是指代不同的事物,而不僅僅是一個字符串水由。由于Name
和Email
是不可互換的荠呐,這么做可以幫助你避免混淆它們。
泛型(Generics)
泛型是一種對類型本身進行抽象的方法。什么意思呢泥张?看看下面的代碼:
type GenericObject<T> = { key: T };
var numberT: GenericObject<number> = { key: 123 };
var stringT: GenericObject<string> = { key: "Preethi" };
var arrayT: GenericObject<Array<number>> = { key: [1, 2, 3] }
我為類型T
創(chuàng)建了一個抽象的概念呵恢,你可以使用任何類型來代替T
。對于numberT
來說媚创,T
是number
類型的渗钉;而對于arrayT
來說,T
是Array<number>
類型的钞钙。
如果你是第一次接觸這些類型鳄橘,確實可能會有些暈。不過相關(guān)的入門介紹馬上就要結(jié)束了芒炼。
Maybe
Maybe
類型允許我們聲明一個包含null
和undefined
兩個潛在類型的值瘫怜。對于類型T
又T
、null
和undefined
三種類型本刽,意味著一個變量可能是T
鲸湃、null
和undefined
三者之一。在類型定義前加上一個“?”就可以定義一個Maybe
類型:
var message: ?string = null;
這段代碼表示message是string
類型子寓、null
或undefined
暗挑。
你也可以用Maybe
類型來表示一個對象屬性可能是某種類型T
或者undefined
:
type Person = {
firstName: string,
middleInitial?: string,
lastName: string,
};
通過將“?”放在屬性名middleInitial
之后,你可以表明這個對象時可選的别瞭。
Disjoint unions(或操作)
這是創(chuàng)建你的數(shù)據(jù)模型的另一個強大的方法窿祥。當你的程序需要同時處理不同的數(shù)據(jù)類型,Disjoint unions會是一個很有用的方法蝙寨。換句話說晒衩,根據(jù)環(huán)境的不同,數(shù)據(jù)的結(jié)構(gòu)也會不同墙歪。
我們基于之前的泛型示例來拓展PaymentMethod
類型听系。想象一種場景,在我們的一個應用中虹菲,包含了三類不同的支付方法靠胜。在這種情況下,你可以這么做:
type Paypal = { id: number, type: 'Paypal' };
type CreditCard = { id: number, type: 'CreditCard' };
type Bank = { id: number, type: 'Bank' };
你可以用disjoint union來定義PaymentMethod
類型:
type PaymentMethod = Paypal | CreditCard | Bank;
現(xiàn)在支付方法將會是這三種類型中的一種毕源。關(guān)于disjoint union浪漠,在第二部分將會有更多的例子。
除了上面所述之外霎褐,F(xiàn)low還有一些其他特性有必要在這篇簡介中提一下:
1)類型推斷(Type inference):可能的話Flow會使用類型推斷的功能址愿。當類型檢查可以自動推斷出一個表達式的數(shù)據(jù)類型時,類型推斷就會介入進來冻璃。這個特性可以幫助避免過多的類型聲明响谓。
舉個例子损合,你可以這么寫:
/* @flow */
class Rectangle {
width: number;
height: number;
constructor(width, height) {
this.width = width;
this.height = height;
}
circumference() {
return (this.width * 2) + (this.height * 2)
}
area() {
return this.width * this.height;
}
}
即使這個類并沒有類型,F(xiàn)low依然可以進行一定的類型檢查:
var rectangle = new Rectangle(10, 4);
var area: string = rectangle.area();
// Flow errors
100: var area: string = rectangle.area();
^^^^^^^^^^^^^^^^ number. This type is incompatible with
100: var area: string = rectangle.area();
^^^^^^ string
在這里我嘗試把area
定義為string
類型娘纷,但是在Rectangle
類中嫁审,我們給width
和height
定義的類型時number
類型。因此赖晶,基于area
方法的定義律适,它只能返回一個number
類型。即使我們有顯式地給area
方法定義類型嬉探,F(xiàn)low還是可以捕獲到這個錯誤擦耀。
需要注意的一點是,F(xiàn)low的維護人員建議涩堤,如果你需要導出(export)類定義眷蜓,為了在非本地上下文環(huán)境(context)下更容易找出錯誤原因,你最好還是要添加明確的類型定義胎围。
2)動態(tài)類型檢查(Dynamic type tests):這個特性意味著吁系,F(xiàn)low有能力確定一個變量在運行時的類型,因此當進行靜態(tài)類型檢查使Flow也可以使用這個能力白魂。當Flow拋出一個錯誤但是你需要讓Flow相信你的代碼是沒有問題的時候汽纤,這個特性就顯得很有用處。
在這里我不會更深入得講解更多細節(jié)福荸,因為這更多的是一個進階的特性蕴坪,針對這一點我希望能夠單開一篇來介紹。但如果你想要了解更多敬锐,可以看看這里背传。
語法介紹結(jié)束
這一部分我們了解了非常多的內(nèi)容。我希望這個總覽性質(zhì)的簡介可以對你有幫助台夺。如果你想要進一步探索径玖,我推薦你可以深入地讀一下這些文檔。
結(jié)束了語法的學習颤介,我們可以進入下一個更有趣的部分 ==> 使用靜態(tài)類型的優(yōu)勢與劣勢
原文:Why use static types in JavaScript? (A 4-part primer on static typing with Flow)