學習資料:拉勾課程《大前端高薪訓練營》
閱讀建議:文章較長,搭配文章的側邊欄目錄進行食用,體驗會更佳哦!
內容說明:本文不做知識點的搬運工,文中只記錄個人對該技術點的認識和理解,
一:編程語言的型別系統
1.強型別語言 與 弱型別語言
語言型別的強弱之分,業界并沒有權威的對比概念,所以網上有很多種不同的說法,但大部分說法的意思都在說明強型別語言有更強的型別約束,而弱型別中幾乎沒有什么約束,
以下個人學習課程之后對強型別與弱型別的理解:
- 強型別語言:程式運行時變數型別不允許任意的隱式型別轉換(型別安全),
- 弱型別語言:程式運行時變數型別允許任意的隱式型別轉換(型別不安全),
最簡單的代碼理解案例:
// 強型別語言:java、python等
100 - '50' //報語法錯誤
// 弱型別語言:javaScript
100 - '50' // 50
2.靜態型別語言 與 動態型別語言
以下個人學習課程之后對靜態型別型別與動態型別的理解:
靜態型別:程式開發時變數型別宣告后,不允許再修改(編譯階段檢查型別),
動態型別:程式開發時變數型別宣告后,可以隨時發生變化(運行階段檢查型別),
最簡單的代碼理解案例:
// 靜態型別語言:java
int a = 100;
a = '100'; // 報語法錯誤
// 動態型別語言:javascript
var a = 100;
a = '100'; // typeof(a) 輸出 'string'
3.主流編程語言分類

二:Javascript型別系統缺陷
1.程式健壯性差
JavaScript語言本身是一種動態弱型別的解釋型語言,在具體探討它的型別缺陷之前,我們先看日常開發當中的幾個JavaScript常見型別錯誤示例:
let fn, name, age = 10, inputAge = '1';
fn() // 運行時報錯,undefined不能當作函式物件不能呼叫
const fn1 = fn; // 潛在錯誤:fn1并不是一個方法
age = age + inputAge;// 運行不準確,但age計算不準確,同時型別轉為string也是一個潛在錯誤
<p> {name} </p> // 運行不準確,用戶看到undefined
從上述代碼我們可以感覺到,我們平常寫的JavaScript程式發生的很多錯誤其實都是型別錯誤,我們在找到這些錯誤并且除錯解決它花了不少時間,盡管如此,程式中依然還有很多潛在的型別錯誤沒有發現,下面思考一波發生這些錯誤的原因:
-
JavaScript是動態型別:意味著在程式開發階段,開發者開發出的源代碼沒有經過型別檢查就直接交給解釋器執行,
-
JavaScript是弱型別:意味著程式在運行階段碰到開發者造成的型別問題時,程式會自作主張的進行隱式型別轉換,無法轉換則報錯,轉換成功則回傳運算結果(有時候不是開發者所期望的運行結果,即運行不準確),
-
JavaScript既是動態型別又是弱型別,這使得JavaScript程式在運行期間很容易發生型別錯誤、隱藏潛在錯誤、以及錯誤不被識別為錯誤導致程式運行不準確,
2.原始解決方案
程式很容易發生型別錯誤、隱藏潛在錯誤、以及錯誤不被識別為錯誤而運行不準確,一個好的開發者絕對無法認同這些事情的存在,所以我們在日常JavaScript開發中,經常會加上一些型別判斷代碼來避免錯誤的發生,如下代碼示例:
// 案例1
const obj = {};
// obj.foo(); // error
if(obj.foo){ // 防錯處理
obj.foo();
} else {
// xxx
}
// 案例2
function sum (a, b) {
return a + b
}
function sum (a, b) { // 防錯處理
if (!(typeOf(a)=== 'int' && typeOf(b)=== 'int')){
throw new TypeError('arguments should be number');
}
return a + b
}
在寫JavaScript時,我們會經常有意的寫這些代碼來避免或者解決型別錯誤,但是我們分析一下這樣解決型別問題的缺點:
- 更大的開發成本:開發者除了需要撰寫代碼的核心邏輯之外,還需要花費更多的時間精力去撰寫避免型別問題的代碼,尤其在封裝給別人使用時,這個問題尤其顯著,
- 只能解區域分問題:我們不可能在每次變數使用前都做一次型別判斷,所以這種方案只能解區域分型別錯誤,
- 大量型別判斷代碼:這種方案需要在程式中撰寫大量型別判斷代碼來避免錯誤,這容易導致代碼量變大、核心業務代碼被隱藏、代碼不夠清晰等問題,
- 性能問題:在運行階段需要運行這些型別判斷邏輯代碼,肯定需要消耗更多的運行時間,
上述解決方案雖然比較丑陋,但是這就是我們平常寫JavaScript代碼避免型別問題的默選方案,
先是講到JavaScript的型別系統這么多缺陷,后是講到原始解決方案還這么敷衍,有沒有突然有一種用JavaScript語言編程好差勁的感覺,難怪往前這么多人稱呼JavaScript為小丑語言,難堪大任!
JavaScript在運行期間出現的這么多型別問題,其實都是源于JavaScript本身對變數型別沒有約束,開發階段無法進行型別檢查,從而使得開發者撰寫出的JavaScript代碼容易出現編程錯誤導致的,
借鑒其它語言的做法,我們可能會想到,開發階段的型別約束會是型別判斷之外的解決JavaScript型別錯誤的另一種方案,實作上,這種思想現在有兩種主流的解決方案,即Flow 和 Typescript,它們都是通過在源代碼上加上型別宣告來實作的型別約束和型別檢查,然后通過編譯得到一份型別嚴謹的JavaScript代碼交給JavaScript解釋器執行的方式來解決的型別問題,這主要涉及到開發、檢查、編譯三個程序:
- 開發階段:開發階段根據規則為變數加上合適的型別宣告,
- 檢查階段:使用檢查工具根據變數的型別宣告和變數值進行匹配檢查,分為開發時檢查(代碼提示)、開發后檢查、編譯時檢查三種,
- 編譯階段:使用編譯工具為變數移除型別宣告而后得到一份型別嚴謹的JavaScript代碼,
3.Flow解決方案
Flow是JavaScript的靜態型別檢查工具,它定義了一套型別約束與檢查規則,在檢查通過之后可以編譯出一套型別嚴謹也沒有Flow型別宣告的JavaScript代碼,解決JavaScript型別問題,下面從開發、檢查到編譯三個階段簡單說明Flow提供的解決方案是如何解決JavaScript型別問題的,具體的使用細節查閱官方檔案即可,
1):開發階段添加型別宣告
添加型別宣告涉及到三個部分,即自己代碼中的型別注解、環境下api(window / node)的型別注解以及第三方庫(引入的lib)中的型別注解,環境下api以及第三方庫的檔案中缺乏型別注解時,我們通常會通過引入型別宣告檔案的方式來解決,
下面是給自己代碼中加上型別宣告的案例:
// @flow
// test.js
function sum(m: number, n: number): number {
return m + n
}
2):檢查階段進行型別匹配
型別檢查分為開發時檢查、開發后檢查、編譯時檢查三種,開發時檢查工具通常是IDE提供的插件,開發后以及編譯時檢查通常是使用官方提供的檢查工具,
Flow的開發時檢查工具此處不做探討,下面簡單說明一下Flow開發后檢查的作業流:
- 安裝檢查工具flow-bin
yarn add flow-bin --dev
- 寫入檢查配置資訊:.flowconifg
# 生成.flowconifg組態檔,在這里可以配置檢查源、檢查規則、檢查輸出位置等
yarn flow init
- 讀取配置執行檢查
yarn flow
- 控制臺輸出檢查結果
- 根據檢查報告修改代碼中型別錯誤
3):編譯階段移除型別宣告
Flow源代碼中的型別注解并不符合JavaScript語法,直接丟給JavaScript解釋器執行會報錯,所以我們需要對源代碼進行編譯移除型別宣告,得到能夠被JavaScript解釋器執行的JavaScript代碼,解決JavaScript型別問題,
移除JavaScript檔案中的Flow型別注解有兩種方案:
- 使用工具庫flow-remove-types移除
# 安裝
yarn add flow-remove-types --dev
# 移除:命令引數,編譯輸入代碼的位置、編譯輸出代碼的位置
yarn flow-remove-types src -d dist
- 使用Babel插件@babel/preset-flow移除
# 安裝
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
# 配置:.babelrc檔案配置preset-flow插件
{
"presets": ["@babel/preset-flow"]
}
# 轉換:命令引數,編譯輸入代碼的位置、編譯輸出代碼的位置
yarn babel src -d dist
三:Typescript終極解決方案
和Flow一樣,TypeScript也是JavaScript的型別檢查器,不同的是,Typescript功能上更強大更完善,生態上也更加健全,在語法上,Typescript是Javascript的超集,它與JavaScript的關系如下圖:

以下是除官話外,個人對Typescript的認識:
Typescript這門語言其實并不能和C、C++、Java、JavaScript這些語言相談并論,它只能算是JavaScript的切面語言,因為它的變數型別和語法規則只涉及到開發和編譯階段,在編譯之后轉換為JavaScript交給JavaScript解釋器執行,這也意味著,我們在學習typescript這門語言的型別和語法時,完全不必要關注它的運行機制與存盤規則,而是只需要理解它與JavaScript型別和語法的映射關系即可,
下面我們從開發編譯兩個階段簡單說明Typescript提供的解決方案是如何解決JavaScript型別問題的,具體的使用細節查閱官方檔案即可,
1.開發階段使用Typescript語法
從上圖我們也可以看到,Typescript除了支持靜態型別之外,還支持ES6語法,接下來我們主要關注它是怎么解決JavaScript的型別問題的,
與Flow一樣,Typescript的型別宣告也涉及到到三個部分,即自己代碼中的型別注解、環境下api(window / node)的型別注解以及第三方庫(引入的lib)中的型別注解,環境下api以及第三方庫的檔案中缺乏型別注解時,我們通常會通過引入型別宣告檔案的方式來解決,
下面是給自己代碼中加上型別宣告的案例:
// test.ts
function sum(m: number, n: number): number {
return m + n
}
開發時檢查
typescript可以歸屬于靜態語言,IDE對其代碼具備很強的感知能力,所以IDE可以為開發者提供很強大的開發時檢查、代碼提示、錯誤提示等功能,
2.編譯階段轉換Typescript語法
與Flow一樣,Typescript源代碼也不能直接交給JavaScript解釋器執行,我們需要使用官方提供的tsc工具將typescript代碼編譯為JavaScript代碼,解決Javascript的型別問題,
以下簡要說明Typescript的編譯作業流:
- 安裝typescript
yarn add typescript --dev
- 寫入編譯配置:tsconfig.json
# 1.生成.flowconifg組態檔,在這里可以配置檢查源、檢查規則、檢查輸出位置等
yarn tsc --init
# 2.修改tyscript組態檔,主要涉及到編譯輸入檔案位置、輸出檔案位置、編譯規則等
# xxx
- 讀取編譯配置執行編譯
yarn tsc
- 編譯結束,成功得到JavaScript代碼,失敗則根據編譯報錯資訊修改代碼,
3.Typescript型別系統
這里簡單提一提Typescript的型別系統,Typescript官網檔案對它的型別劃分為以下幾類:
| 型別 | 含義 | 示例 |
|---|---|---|
| basic Types | 基本型別 | number、Tuple、Void… |
| Interfaces | 介面型別 | { x : ‘xx’ } / interface xxx { x ‘xx’} |
| Unions and Intersection Types | 并集、交集型別 | object | null |
| Literal Types | 字面量型別 | 1 | 2 | 3 |
| Enums | 列舉型別 | Enum x { x ‘1’ } |
| Functions | 函式型別 | function (m: number): Void { // xxx,no return } |
| Classes | 型別別 | 完整的java類,訪問控制,單繼承多實作 |
| Generics | 泛型 | 值泛型:Array<number>,函式泛型、類泛型 |
Typescript作為新出現的靜態語言,它的型別系統吸收了很多其它語言中優秀的型別,尤其是Java,其實對于這些各種型別約束,我們也可以等同的使用原始解決方案為代碼加上型別判斷來解決型別問題,使用typescript雖然會造成初期開發增加,但是它可以讓我們不用大量的型別判斷就可以寫的一手型別嚴謹、性能更好、維護性更好的JavaScript代碼,如果想做一位JavaScript的優秀coder,不用我說也會擁抱Typescript吧!
對于typescript的學習成本,其實它的學習成本很低,特別是對于有靜態語言使用經驗的開發者來說,因為它只涉及到開發和編譯階段,我們只需要理解它的各種型別含義并熟悉運用就可以寫的一手好Typescript代碼,
本文結束,謝謝觀看,
如若認可,一鍵三連,每周一篇,后續更多精彩,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/227185.html
標籤:其他
下一篇:C語言宏定義使用
