幾個月前把 ES6 的特性都過了一遍,收穫頗豐。如今繼續來看看 TypesScript(下文簡稱爲 「TS」)。限於經驗,本文一些總結若有不當,歡迎指正。前端
官網有這樣一段描述:java
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.es6
說的是 TS 是 JS 的超集,而且能夠編譯成普通的 JS。web
其中, 超集 的定義是:typescript
若是一個集合 S2 中的每個元素都在集合 S1 中,且集合 S1 中可能包含 S2 中沒有的元素,則集合 S1 就是 S2 的一個超集,反過來,S2 是 S1 的子集。npm
而實際上,「超出」 的部分主要就是 「類型系統」。所以能夠這樣概括:編程
TS ≈ ES6 + 類型系統json
ES6 是 ES5 轉向主流語規格的一個重要升級,順着這個角度看,TS 讓這門語言披上一層類型的外衣,直接演變成一種強類型的語言;從相反角度看,TS 將編程語言們一些主流的特性引入到了 JS 的世界。gulp
TypeScript 設計巧妙,兼具微軟工業化的水準。首先,它僅靠一行命令,就融入到了廣大前端人的世界:編程語言
npm install -g typescript
而後由你隨便挑一個曾編寫的 .js
腳本文件(不妨叫作hello.js
),不用對內容作任何修改,直接將文件後綴改爲 .ts
。這樣,你就已經完成了一份 TypeScript 腳本的編寫!
而後編譯它:
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); // 編譯後的 ES6 let 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 學習上暢通無阻的敲代碼~