【轉】JavaScript => TypeScript 入門

幾個月前把 ES6 的特性都過了一遍,收穫頗豐。如今繼續來看看 TypesScript(下文簡稱爲 「TS」)。限於經驗,本文一些總結若有不當,歡迎指正。前端


官網有這樣一段描述:java

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.es6

說的是 TS 是 JS 的超集,而且能夠編譯成普通的 JS。web

而實際上,「超出」 的部分主要就是 「類型系統」。所以能夠這樣概括:typescript

TS ≈ ES6 + 類型系統npm

ES6 是 ES5 轉向主流語規格的一個重要升級,順着這個角度看,TS 讓這門語言披上一層類型的外衣,直接演變成一種強類型的語言;從相反角度看,TS 將編程語言們一些主流的特性引入到了 JS 的世界。編程

平穩過渡json

TypeScript 設計巧妙,兼具微軟工業化的水準。首先,它僅靠一行命令,就融入到了廣大前端人的世界:gulp

npm install -g typescript

而後由你隨便挑一個曾編寫的 .js 腳本文件(不妨叫作hello.js),不用對內容作任何修改,直接將文件後綴改爲 .ts。這樣,你就已經完成了一份 TypeScript 腳本的編寫!segmentfault

而後編譯它:

tsc hello.ts

OK,你已經平滑過渡到了 TS 的世界。就是這麼簡單!

固然這只是「一小步」,彷佛後邊還有無數的坑要填。不用擔憂,TS 已經填平了大部分的坑!

好比,時下最流行的 gulp,webpake 工具,只需作一些簡單的配置,就能接引入TypeScript 進行編譯;同時爲了能與 React 完美融合,TS 引入了與 JSX 相似的 TSX 語法。固然,TS 在 Angular、Vue.js 以及 Node.js 中也是暢通的…

坑都填平了,你們過渡起來天然順心順手。

基本類型

與 ES6 一脈相承的,同時也接軌大部分強類型語言,TS 的類型大概有這些:

1),Number、Boolean、String、Null 、undefined、Symbol

2), Array、Function、Object

3),Tuple、enum、Void、 Never、Any

TS 做爲 JS 的一個超集,在 JS 的基礎上擴展了一些很是有用的類型。第 3)中的類型就是從一些強類型語言引入的類型。

爲了由簡入繁,不妨將這些類型劃分爲:基本類型、複合類型。複合類型 通常由 基本類型 構成。如下將漸進式的對 TS 的這些類型進行了解。

如何作類型聲明?

強類型語言都有一套類型聲明的語法規則,TS 也不例外。TS 採用類型註釋的寫法,像這樣將一個帶冒號的註釋,置於聲明變量名以後,就構成了 TS 類型聲明的語法。

let str : string = 'hello typescript';

JAVA 的寫法是相反的,但無實質差異:

String str = 'hello java';

這樣的註釋如同一種補充說明,後文將簡稱它爲 「冒號註釋」,熟悉書寫規則,有利於快速進入到 TS 的代碼世界。

實際上,ES6 有一種屬性描述對象,是經過Object.getOwnPropertyDescriptor(obj, key) 獲取的。

let obj = { set name(val) {}}Object.getOwnPropertyDescriptor(obj, 'name');// {// configurable: true// enumerable: true// get: undefined// set: ƒ a(val)// }

若是將 setter 類型的 name 方法適當改寫,咱們甚至能夠實現 obj.name 賦值的類型檢查功能,也很是有意思。

一樣的,冒號註釋 : string 也能夠理解爲對一個 str 變量的描述。憑藉這個註釋的描述,TS 的類型編譯器就能進行類型檢查了。

創建了類型的認知後,繼續跑馬圈地,鞏固認知。其中,Function、 Never、Any 規則稍顯複雜,但也沒有什麼特別的,留後細說。

簡單的基本類型

// boolean 類型let isBool: boolean = 1 < 5;// string 類型let str: string = 'hello world';// number 類型let num: number = 123;// void 類型let unusable: void = undefined;// undefined 類型let u: undefined = undefined;// null 類型let n: null = null;//Symbol 類型// 類型 symbol 小寫也能編譯經過let sym: Symbol = Symbol('hello');

簡單的複合類型:

// object 類型let obj : object = {};let arrObj : object = [];let funcObj : object = () => {};// array 類型let arrNum : number[] = [1, 2, 3]let arrStr : string[] = ['a', 'b', 'c']let arrObj : object[] = [{}];// 元組 類型let tup : [number, string] = [1, 'hello'];// 枚舉類型enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

可謂盡收眼底,類型的語法就是冒號註釋,僅憑這一條,60~70% 的狀況你都無需擔憂本身的類型書寫有誤。

一個靈活的子類型

但 JS 的動態類型太靈活了,null 和 undefined 的類似性, Array、Function 和 Object的糾纏不清的關係,僅憑一招恐怕還很難駕馭的住 JS 的 「多動症」 般的類型。好比:

// boolean 類型接收這樣的賦值let isBool_n: boolean = null;let isBool_u: boolean = undefined;// void 類型接收這樣的賦值let unusable: void = undefined;unusable = null;// Symbol 類型接收這樣的賦值let sym: Symbol = Symbol('hello');sym = null;sym = undefined;// object 類型接收這樣的賦值let obj : object = {};obj = null;obj = undefined;

它們都能編譯經過。可是 null 不屬於 boolean 類型,undefined也並不屬於object 類型,爲何能經過類型檢查?

事實上,undefined 和 null 是全部類型的子類型。也就是說,它們倆能夠做爲值賦給任何類型的變量。甚至,它們倆能夠互相賦值給對方。

// undefined 類型let u: undefined = null;// null 類型let n: null = undefined;

有了這一條規則,就能解釋一些 「複合類型」 中遇到的問題:

let arrNum: number[] = [];let arrStr: string[] = [];// undefined 也屬於 number 類型let arrNum: number[] = [undefined];// undefined 也屬於 object 類型let obj : object = undefined;

有了這條規則,咱們能夠大膽的寫 TS 的類型聲明瞭。

但太過放開的規則——本文姑且稱之爲 「混雜模式」,又彷佛一會兒讓 TS 退回到了 JS 的動態類型的原始狀態了,讓習慣了強類型的同窗容易懵掉,也讓從 JS 轉 TS 的同窗體會不到強類型的好處。

畫條界限

好在,TS 設計了一套巧妙的類型系統,猶如給 JS 披上 了一層強大的盔甲。

TS 在 「混雜模式」 下,可能存在這樣的風險,就是:編譯正確,運行出錯。好比:

// 無心得到一個 undefined 做爲初始值let init_name = undefined;let nameList: string[] = [init_name];console.log(nameList[0].split('_')); // 運行報錯

在非 「嚴格模式」 下,上述 TS 代碼編譯無誤,可是真正拿到頁面去運行編譯結果時,出現錯誤。

那怎麼辦呢?要相信 TS 強大的類型系統,只需一項配置,就能將編譯切換成 「嚴格模式」:

// 在配置文件 tsconfig.json 中增長一項"compilerOptions": { // ... "strictNullChecks": true },

再次執行編譯,就會出現錯誤提示信息:

error TS2322: Type 'undefined[]' is not assignable to type 'string[]'.

TypeScript 官方教程鼓勵儘量地使用 --strictNullChecks,所以這裏也強烈建議配置該屬性再進行編譯,這樣能很好的發揮 TS 類型檢查的做用。

網開一面和漏網之魚

TS 編譯經過指的是類型檢查符合類型系統的規則,運行 OK 則是編譯後的 JS 自己執行無誤。編譯經過,不等於運行OK,即便在 「嚴格模式」 下也是這樣的,因此千萬別覺得編譯經過了就完事了。

以 Any 類型爲例,在 --strictNullChecks 模式下:

// TS 代碼let anyThing: any = 'hello';console.log(anyThing.myName);// 編譯後的 ES6let anyThing = 'hello';console.log(anyThing.setName('world'));

很顯然,編譯後的 anyThing.setName('world') 會運行報錯。

固然, Any 類型略有點特殊,由於它能夠當作是 TS 平滑退化到 JS 的一個類型,官網教程也有這樣解說:

在對現有代碼進行改寫的時候,any類型是十分有用的,它容許你在編譯時可選擇地包含或移除類型檢查。

那問題又回來了,是否除了 Any 類型,其餘編譯OK,代碼就運行無錯呢?鑑於筆者正在入門,經驗有限,不敢給這個結論。但不管如何,類型檢查是能夠排除大部分錯誤的。

最後,編譯的時候,儘可能選擇編譯成 ES6 (前提是項目是用 ES6 寫的)。配置是:

"compilerOptions": { "target": "es6" // "es5"}

只有一條規則

TS 「冒號註釋」 ——就這一條規則,貫穿始終。在函數的類型聲明中,繼續來鞏固這條規則的寫法。

類型聲明只對變量負責,對於函數,需考察輸入——函數參數(也是變量)、輸出——函數返回值兩個要素。

由於函數的特殊結構,全部 「冒號註釋」 規則的寫法要特別瞭解下:

// 聲明函數function add(x: number, y: number): number { return x + y;}// 函數直接量let myAdd = function(x: number, y: number): number { return x + y; };

能夠看到,參數的 「冒號註釋」 和通常變量沒有任何差異。卻是函數輸出類型註釋有點特別——試想,: number 緊隨函數名以後或者 function 關鍵字以後,是否是容易被誤解爲函數名的一部分?是否是對編譯引擎不太友好?從這個角度看,註釋置於) 以後最爲合理。

對了,一個疑問一直從頭保留到如今:要是一個變量是 function 類型,那類型註釋怎麼寫,又不能拿 function 關鍵字去作類型註釋?

let myAdd: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };

其中等號前邊的 : (x: number, y: number) => number 就表明了函數類型。它仍然在結構上符合 「冒號註釋」 的規則,只不過冒號後邊是一串表達式。這樣的結構有點像 Python 中的推導式的概念。

好了,補上這一塊重要的缺漏,本文就完成了全部基本類型的類型聲明的解釋。憑藉一條規則,但願在 TS 學習上暢通無阻的敲代碼~


做者:https://segmentfault.com/u/webyoung

相關文章
相關標籤/搜索