TypeScript是啥,有人說TypeScript = Type + Script,實際我以爲更準確的應該是TS = Java(JS)或者 TS = C#(JS),使用Java/C#的語法寫JS,而且爲了能讓JSer能更容易接受,它的語法又不能直接把Java/C#的那套搬過來,要貼近於JS. 因此官方說法是TS是JS的超集,超的地方就是引入了Java/C#的語法特性(可是官方又不認可)。javascript
TypeScript出現的目的是什麼,以下官方說法:java
We designed TypeScript to meet the needs of the JavaScript programming teams that build and maintain large JavaScript programs.git
即給大型的JS應用程序使用和維護,而且它不是爲了提供一個一定是正確的類型系統,而是在正確性和生產力之間找到了一個平衡:github
Not Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.typescript
因此這裏官方彷佛已經對要不要用TypeScript給了一個答案:適合於大型的JS項目,不用的話直接寫JS生產力會更高,用了的話犧牲了部分生產力可是換來了正確性,但也不是保證100%準確的。這個正確性體如今哪裏呢?vim
TS是一種強類型語言,變量須要指定類型(內置或者自定義),不一樣類型的變量不能相互賦值,這個能夠提早發現一些運行時錯誤,例如當往localStorage寫數據的時候,類型必須得是字符串,這也是不少人不當心就犯的錯誤,以下代碼:設計模式
const storageManager = {
set (key, value) {
try {
window.localStorage.setItem(key, value);
} catch (e) {
console.error(e);
}
},
get (key) {
return window.localStorage[key];
}
};
storageManager.set('pos', {x: 5, y: 8});
storageManager.get('pos'); // 這裏取出來的值是[object Object]複製代碼
而藉助TS可以解決這種狀況,就是經過設定參數的類型,以下代碼所示:編輯器
const storageManager = {
// value必須得是string類型
set (key, value: string) {
try {
window.localStorage.setItem(key, value);
} catch (e) {
console.error(e);
}
}
};
storageManager.set('pos', {x: 5, y: 8});複製代碼
給參數value添加了一個string類型的限定,這段代碼在使用ts編譯成js的時候將會報錯:函數
意思是說兩個類型不匹配,不能賦值。因此咱們不用運行也不用取出localStorage裏的東西也可以知道這裏發生了問題。可是這樣就能保證萬無一失了嗎,以下面的例子:性能
// 從某個地方獲取到數據,如input輸入框,或者是url的參數
let data: string = '{"x": 5, "y": 8}';
storageManager.set('pos', JSON.parse(data));
storageManager.get('pos'); // 這裏取出來的值是[object Object]複製代碼
咱們從輸入框或者url參數取到了字符串的數據,而後在傳遞參數的時候用了JSON.parse(例如誤覺得須要傳Object),這個時候咱們再將從新編譯,TSC就不會報錯,可以經過,可是運行結果依據悲劇。因此你可能還得這麼作,在函數裏面判斷參數類型:
const storageManager = {
set (key, value) {
if (typeof value !== 'string') {
throw new Error('value should be type of string');
}
// 其它代碼略
}
};複製代碼
一旦有人傳進來的參數不是string,那麼控制檯馬上拋異常。雖然是運行時才發現的錯誤,可是你總不能說我寫了段代碼連跑一下都沒有就提交代碼了吧。
這裏咱們也看到TypeScript強類型的缺陷,它是一種外掛的強類型,沒法判斷一些原生API的類型,因此和內置語言層面的強類型仍是有差別的。
還有一種常見的錯誤是把API拼錯,以下面的例子:
let data: string = 'Version-1.3.1';
storageManager.set('pos', data.toLowerCace());複製代碼
編譯這段代碼將會提示:
這裏咱們把toLowerCase拼錯了,TSC給了咱們提示說ES5的String是沒有toLowerCace的。這確實也是使用TS的好處,就好像你在寫Java的時候,若是你寫了一個不存在的方法,IDE會直接標紅。一樣地提早發現了一些須要在運行才能發現的錯誤。這個還有點像ESLint的no-undef規則,當咱們把一個變量名寫錯或者忘記import就直接用的時候就會致使該變量undefined,eslint檢查便會給出提示。可是和ESLint的區別在於,ESLint檢查的仍然是原生JS語法,不須要附加一個TS的語法,只是ESLint不檢查方法名是否存在。
那麼這種方法名寫錯的機率到底有多高?實際上JS的API並無幾個,這也正是我寫JS一直使用vim編輯器的緣由,而以前寫Java(Spring MVC)的時候便使用了IDE。
這種強類型附帶的好處即是IDE會提示API,以下Sulime編輯提示string的方法:
但實際上這個問題並不大,只要某個單詞當前文件出現過一次以後,那麼下次即可使用自動補全,不論是vim仍是其它編輯器。
另外TS還對API作了DOCS,以下圖所示:
這樣咱們就不用去查文檔,而對本身寫的類型也能夠添加相應的DOCS,方便在輸入的時候即可知道函數的做用是什麼,參數和返回值分別又是什麼。但實際上JS也是能夠的,以下圖所示:
只要你在定義變量的時候給出一個初始值,便能使用TS的文檔提示(編輯器安裝TS的提示插件),因此從這點看的話只是出於自動提示目的也能夠不寫TS.
除了類型以外,TS還提供了一套完整的OOP實現。
ES5的原型繼承在其它面嚮對象語言如Java/C++等來看是一種很獨特的存在,它連類的私有變量、公有變量的概念都沒有,更別提什麼接口類、抽象類、虛函數這些OOP必備的東西。可是沒關係,能夠寫TS,TS還你一個真正的OOP,讓你從新找回OOP的感受,進而實現各類設計模式。
首先連私有變量的概念都沒有,怎麼能算是OOP封裝呢,因此TS具有公有(public)、私有(private)、保護(protected)三種類型屬性,其中公有屬性能被類的實例所訪問,而私有沒法實例所訪問,只能在類定義的方法裏面被訪問到,而保護類型的雖然沒法被類實例所訪問,可是可以被繼承的類所訪問。這個特性和其它OOP語言對齊。
若是看過《Head Frist設計模式》這本書的讀者應該知道,它裏面是用Java寫的,幾乎全部的設計模式都藉助了抽象類和接口類進行實現,因此在Java裏面,若是沒有抽象類/接口類的存在彷佛就玩不了設計模式。例如書中的策略模式的例子是這樣的,有這麼一個需求,實現兩種鴨子,一種是會飛的,另外一種是不會飛的,而且一隻鴨子能夠隨時切換飛行的行爲,如從不會飛切到會飛,也就是說這種飛行的行爲就是一種策略,切換飛行行爲就是切換策略,咱們可依照書中的代碼用TS實現一遍,以下代碼所示:
// 因爲有兩種不一樣的飛行行爲,因此要有一個接口類
interface FlyBehaviour {
fly () : void;
}
// 會飛的行爲,實現怎麼飛
class FlyWithWings implements FlyBehaviour {
fly () : void {
console.log('I\'m flying');
}
}
// 不會飛的行爲
class FlyWithNoWays implements FlyBehaviour {
fly () : void {
console.log('I can\'t fly');
}
}複製代碼
飛行的策略都是一種叫作FlyBehaviour的自定義類型,由於它們要能賦值給同一個變量,因此須要使用一個接口類。
而後在鴨子類裏面使用這種自定義類型,以下代碼所示:
// 因爲有不一樣類型的鴨子,有相同和不一樣行爲,因此我須要寫一個抽象類
abstract class Duck {
// 它組合了一個飛行策略
protected flyBehaviour: FlyBehaviour;
quak () : void {
console.log('quak');
}
abstract performFly () : void; // 必須在派生類中實現
}
// 如今有一種野鴨,它是一種鴨子,因此用繼承
class MallardDuck extends Duck {
constructor () {
super();
// 默認不會飛
this.flyBehaviour = new FlyWithNoWays();
}
// 能夠隨時改變策略
setFlyBehaviour (flyBehaviour) {
this.flyBehaviour = flyBehaviour;
}
performFly () : void {
this.flyBehaviour.fly();
}
}複製代碼
驅動代碼以下所示:
let mallardDuck = new MallardDuck();
mallardDuck.performFly(); // 輸出I'cant fly
// 改變飛行策略
mallardDuck.setFlyBehaviour(new FlyWithWings());
mallardDuck.performFly(); // 輸出I'm flying複製代碼
這樣咱們就實現了一個策略模式,這幾段代碼看起來很是的OOP,什麼接口類、抽象類、繼承、實現都用上了,代碼看起來也十分地高大上,但實際上,咱們真的須要寫這麼多類的代碼嗎?若是是個人話,我可能會這麼實現:
// fly-behaviour.js
const flyBehaviour = {
flyWithWings () {
console.log('I\'m flying');
},
flyWithNoWay () {
console.log('I can\'t fly');
}
};
export default flyBehaviour;
// import flyBehaviour from 'fly-behaviour.js';
class MallardDuck {
constructor () {
this.flyType = 'flyWithNoWay';
}
setFlyBehaviour (type) {
if (typeof flyBehaviour[type] === 'undefined') {
throw new Error('flying type is not support');
}
this.flyType = type;
}
performFly () {
flyBehaviour[this.flyType]();
}
};
let mallardDuck = new MallardDuck();
mallardDuck.performFly(); // 輸出I'cant fly
mallardDuck.setFlyBehaviour('flyWithWings');
mallardDuck.performFly(); // 輸出I'm flying複製代碼
上面的代碼的思想是使用一個Object表示策略模式,經過不一樣的類型區分不一樣的策略。實際上在實際的寫代碼過程當中咱們發現不少類其實只須要實例化一次,而JS的Object正好能夠表示這種實例化一次的對象。因此這裏的策略模式咱們連一個類都沒有寫。真正須要寫類的可能就是那種每一個對象須要有本身的數據,不一樣實例之間的數據不同,例如上面的Duck,不一樣的Duck有不一樣的飛行行爲,在寫遊戲模擬外部世界,或者是UI的彈框類等常常須要使用類來實現。
還有一個問題是若是TS的強類型真的這麼好的話,那麼ES標準爲何不規定呢,例如在let/const上再增長string/number之類的定義變量的方式(咱們看到私有變量已經有了),而是須要別人給它加一個外掛
你可能已經看了不少篇介紹TS是如何地優秀,如何地牛逼的文章,忽然發現我這一篇的味道不太同樣。但我並非說TS很差,而只是反對TS「政治」正確性的言論,寫TS就是對的,寫JS就是不提倡的,正如寫Vue/React就是對的,寫jQuery就是不對的。固然我也建議去學習TS,例如若是連什麼是枚舉類型,什麼是泛類型/模板類型,什麼是抽象類/虛類這些概念都沒有,可是又不想去學習Java/C++這種典型的強類型和OOP語言的話,那麼學習TS是頗有意義,無論用或不用。