本篇內容摘抄自如下來源:javascript
只梳理其中部分知識點,更多更詳細內容參考官網。html
今天來說講有 Java 基礎轉 JavaScript 的福音:TypeScriptjava
若是學習 JavaScript 以前已經有了 Java 的基礎,那麼學習過程當中確定會有不少不習慣的地方,由於 JavaScript 無論是在語法上面、仍是編程思想上與 Java 這類語言都有一些差別。node
下面就大概來看幾個方面的差別:es6
JavaScript 是弱語言,聲明變量時無需指明變量的數據類型,運行期間會自動推斷,因此聲明方式很簡單:web
var a = 1; var wx = "dasu_Android"
Java 是強類型語言,聲明變量時必須明確指出變量數據類型:typescript
int a = 1; String wx = "dasu_Android";
弱類型語言雖然比較靈活,但也很容易出問題,並且須要一些額外的處理工做,好比函數期待接收數組類型的參數,但調用時卻傳入了字符串類型,此時 js 引擎並不會報錯,對於它來講,這是合理的行爲,但從程序、從功能角度來看,也許就不會按照預期的執行,因此一般須要在函數內部進行一些額外處理,若是沒有額外處理,那麼因爲這種參數類型致使的問題也很難排查。npm
JavaScript 的變量在 ES5 只有全局做用域和函數內做用域,ES6 新增了塊級做用域。編程
Java 的變量分:類變量和實例變量,屬於類的變量若是是公開權限,那麼全部地方都容許訪問,屬於實例的變量,分紅員變量和局部變量,成員變量在實例內部全部地方均可以訪問,在實例外部若是是公開權限,可經過對象來訪問,局部變量只具備塊級做用域。json
由於 JavaScript 在 ES5 時並無塊級做用域,有些場景下會致使變量被覆蓋的狀況,因爲這種狀況形成的問題也很難排查,好比:
function aaa() { var i = -1; for (var i = 0; i < 1; i++) { for (var i = 1; i < 2; i++) { } console.log(i); } console.log(i); }
在 Java 中,兩次 i 的輸出應該 0, -1,由於三個地方的 i 變量並非同一個,塊級做用域內又生成一個新的局部 i 變量,但在 JavaScript 裏,ES5 沒有塊級做用域,函數內三個 i 都是同一個變量,程序輸出的是:2,3。此時就發送變量被覆蓋的狀況了,。
並且,JavaScript 的全局變量會被做爲全局對象的屬性存在,而在 JavaScript 裏對象的屬性是容許動態添加的,這就會致使一個問題:當使用某變量,但拼寫錯誤時,js 引擎並不會報錯,對它來講,會認爲新增了一個全局對象的屬性;但從程序,從功能角度來看,經常就會致使預期外的行爲,而這類問題也很難排查,好比:
var main = "type-script"; function modify(pre) { mian = `${pre}-script`; } modify(123);
在 Java 裏會找不到 mian 變量報錯,但在 JavaScript 裏 mian 會被當作全局對象的屬性來處理。
並且,JavaScript 的變量容許重複申請,這樣一來,全局變量一旦多了,很容易形成變量衝突問題,這類問題即便在運行期間也很難被發現和排查,好比:
//a.js var a = 1; //b.js var a = "js";
在不一樣文件中,若是全局變量命名同樣,會致使變量衝突,但瀏覽器不會有任何報錯行爲,由於對它來講,這是正常的行爲,但對於程序來講,功能可能就會出現預期外的行爲。
JavaScript 是基於原型的繼承,原型本質上也是對象,因此 JavaScript 中對象是從對象上繼承的,同時對象也是由對象建立的,一切都是對象。
Java 中有 class 機制,對象的抽象模板概念,用於描述對象的屬性和行爲以及繼承結構,而對象是從類實例化建立出來的。
正是由於 JavaScript 中並無 class 機制,因此有 Java 基礎的可能會比較難理解 JavaScript 中的繼承、實例化對象等原理。
那麼在面向對象的編程中,自定義了某個對象,並賦予它必定的屬性和行爲,這樣的描述在 Java 裏很容易實現,但在 JavaScript 裏卻須要經過定義構造函數,對構造函數的 prototype 操做等處理,語義不明確,不怎麼好理解,好比定義 Dog 對象:
function Dog() {} Book.prototype.eat = function () { //... } Book.prototype.name = "dog";
對於習慣了 Java 的面向對象編程,在 JavaScript 裏自定義一個 Dog 對象的寫法可能會很不習慣。
JavaScript 雖然在 ES6 中加入了 class 寫法,但本質上只是語法糖,並且從使用上,仍舊與 Java 的 class 機制有些區別,好比:
class Animal { constructor(theName) { this.name = theName; this.ll = 23; } move(distanceInMeters = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
以上是 JavaScript 中 ES6 自定義某個類的用法,與 Java 的寫法有以下區別:
JavaScript 裏沒有 public 這些權限修飾符,對於對象的屬性,只能經過控制它的可配置性、可寫性、可枚舉性來達到一些限制效果,對於對象,可經過控制對象的可擴展性來限制。
Java 裏有 package 權限、publick 權限、protection 權限、private 權限之分,權限修飾符可修飾類、變量、方法,不一樣權限修飾符可讓被修飾的具備不同的權限限制。
在 JavaScript 若是要實現對外部隱藏內部實現細節,大多時候,只能利用閉包來實現。
JavaScript 雖然在 ES6 中引入了 class 的寫法,但本質上只是語法糖,並無相似 Java 中抽象類、抽象方法的機制存在,即便要模擬,也只能是定義一些拋異常的方法來模擬抽象方法,子類不實現的話,那麼在運行期間就會拋異常,好比:
//不容許使用該構造函數建立對象,來模擬抽象類 function AbstractClass() { throw new Error("u can't instantiate abstract class"); } //沒有實現的抽象方法,經過拋異常來模擬 function abstractMethod() { throw new Error("abstract method,u should implement it"); } //定義抽象方法,子類繼承以後,若是不本身實現,直接使用會拋異常 AbstractClass.prototype.onMearsure = abstractMethod; AbstractClass.prototype.onLayout = abstractMethod;
相比於 Java 的抽象類的機制,在編譯期間就能夠報錯的行爲,JavaScript 的運行期拋異常行爲效果可能無法強制讓全部開發者都能正確實現抽象方法。
JavaScript 因爲沒有 class 機制,又是基於原型的繼承,運行期間原型還可動態變化,致使了在 JavaScript 裏沒有一種完美的方式能夠用來獲取對象的標識,以達到區分不一樣對象的目的。
Java 中的對象都是從類實例化建立出來的,所以經過 instanceof 便可判斷不一樣對象所屬類別是否一致。
在 JavaScript 中,只能根據不一樣使用場景,選擇 typeof,instanceof,isPrototypeOf(),對象的類屬性,對象的構造函數名等方式來區別不一樣對象所屬類別。
正是因爲 JavaScript 裏沒有 class 機制,沒有哪一種方式能夠完美適用全部須要區分對象的場景,所以在 JavaScript 中有一種編程理念:鴨式辯型(只要會游泳且嘎嘎叫的鳥,也能夠認爲它是鴨子)
意思就是說,編程中不要從判斷對象是不是預期的類別角度出發,而是從判斷對象是否具備預期的屬性角度出發。
因此,對於若是有 Java 基礎的,JavaScript 學習過程可能會有些不習慣,那麼若是是 TypeScript 的話,能夠說是個福利,由於 TypeScript 不少語法和編程思想上都跟 Java 很相似,很容易就理解。
那麼,來認識下,TypeScript 是什麼?
TypeScript 是 JavaScript 的超集,超集是什麼意思,就是說,JavaScript 程序能夠不加修改就運行在 TypeScript 的環境中,TypeScript 在語法上是基於 JavaScript 進行擴展的。
那麼,TypeScript 在 JavaScript 語法基礎上作了哪些擴展呢?其實就是加入了各類約束性的語法,好比加入了相似強類型語言的語法。
好比說,聲明變量時,須要指定變量的數據類型的約束,以此來減小類型錯誤致使的問題。
let wx:string = "dasu_Android";
其實,本質上是由於 JavaScript 是解釋型語言,由於沒有編譯階段,不少問題只能是運行期纔可能被發現,而運行期暴露的問題也不必定能夠很好的排查出來。
而 TypeScript 語法編寫的 ts 文件代碼,瀏覽器並不認識,因此須要通過一個編譯階段,編譯成 js 文件,那麼 TypeScript 就提供了一個編譯過程,加上它語法上的支持,在編譯期間編譯器就能夠幫助開發者找出一些可能出錯的地方。
舉個例子:
var main = "type-script"; function modify(pre) { mian = `${pre}-script`; } modify(123);
這個例子中,定義了一個全局變量和一個函數,函數本意是接收一個字符串類型的值,而後修改這個全局變量的值,但開發者可能因爲粗心,將全局變量的變量名拼寫錯誤了,並且調用方法時並無傳入字符串類型,而是數字類型。
若是是在 JavaScript 中,這段代碼運行期間並不會報錯,也不會致使程序異常,js 解釋器會認爲它是合理的,它會認爲這個函數是用來增長全局對象的 mian 屬性,同時函數參數它也不知道開發者但願使用的是什麼類型,它全部類型都接受。
因爲程序並無出現異常,即便運行期間,開發者也很難發現這個拼寫錯誤的問題,相反,程序因爲拼寫錯誤而沒有執行預期的功能時,反而會讓開發者花費不少時間來排查緣由。
但這段代碼若是是用 TypeScript 來寫:
這些基礎的語法錯誤,編譯器甚至不用進入編譯階段,在開發者剛寫完這些代碼就能給出錯誤提示。並且,一些潛在的可能形成錯誤的代碼,在編譯階段也會給出錯誤提示。
雖然 TypeScript 語法上支持了不少相似於 Java 語言的特性,好比強類型約束等,但 JavaScript 本質上並不支持,能夠看看上面那段代碼最後編譯成的 js 代碼:
var main = "type-script"; function modify(pre) { mian = `${pre}-script`; } modify(123);
發現沒有,編譯後的代碼其實也就是上述舉例的 js 代碼段,也就是說,你用 JavaScript 寫和用 TypeScript 寫,最後的代碼都是同樣的,區別在於,TypeScript 它有一個編譯階段,藉助編譯器能夠在編譯期就能發現可能的語法錯誤,不用等到運行期。
將 TypeScript 編寫的 ts 文件編譯成 js 文件有兩種途徑,一是藉助命令,二是藉助開發工具。
若是電腦已經有安裝了 node.js 環境,那麼能夠直接執行下述命令:
npm install -g typescript
而後打開終端,在命令行執行:
tsc xxx.ts
tsc 命令就能夠將 ts 文件編譯輸出 js 文件了。
我選擇的開發工具是 WebStrom,這個開發工具自己就是支持 TypeScript 的了,若是你有嘗試過查看 ES五、ES6 相關 api,你可能會發現:
.d.ts 文件就是用 TypeScript 編寫的,因此若是你熟悉 TypeScript 的語法,這些代碼就能很清楚了,.d.ts 是一份聲明文件,做用相似於 C++ 中的 .h 文件。
在 WebStrom 中右鍵 -> 新建文件中,能夠選擇建立 TypeScript 的文件,能夠設置 FileWatcher 來自動編譯,也能夠將項目初始化成 node.js 項目,利用 package.json 裏的 scripts 腳本命令來手動觸發編譯。
我選擇的是後者,若是你對 package.json 或 FileWatcher 配置不熟悉,能夠參考以前模塊化那篇最後對這些配置的介紹。
而編譯器在編譯過程,相似於 Android 裏的 Gradle,能夠設置不少配置項,進行不一樣的編譯,而 TypeScript 編譯過程對應的配置文件是 tsconfig.json
TypeScript 中文網 裏對於這份配置文件的描述很清楚了,這裏摘抄部份內容:
示例:
{ "compilerOptions": { "module": "commonjs", //編譯輸出的 js 以哪一種模塊規範實現,有 commonjs,amd,umd,es2015等等 "target": "es5", //編譯輸出的 js 以哪一種 js 標準實現,有 es3,es5,es6,es2015,es2016,es2017,es2018等 "sourceMap": false, //編譯時是否生成對應的 source map 文件 "removeComments": false, //編譯輸出的 js 文件刪掉註釋 "outDir": "./js/dist" //編譯輸出的 js 路徑 }, "exclude": [ //編譯時排除哪些文件 "node_modules" ] }
最後來看看一些基礎語法,你會發現,若是你有 Java 基礎,這些是多麼的熟悉,用 TypeScript 來編寫 js 代碼是多麼的輕鬆。
ES6 中的數據類型是:number,string,boolean,symbol,null,undefined,object
TypeScript 在此基礎上,額外增長了:any,void,enum,never
TypeScript 中的數據類型是用於類型聲明服務的,相似於 Java 中定義變量或聲明方法的返回值時必須指定一個類型。
ES5 中聲明變量是經過 var,而 ES6 中引入塊級做用域後新增了 let 和 const 的聲明方式,TypeScript 建議聲明變量時,都使用 let,由於 var 會很容易形成不少問題,無論是全局變量仍是函數做用域的局部變量。
先來看看原始類型的聲明:
let num:number = 1; //聲明number類型變量 let str:string = "ts"; //聲明string類型變量 let is:boolean = true; //聲明boolean類型變量 function f(name: string, age: number):void { //函數參數類型和返回值類型的聲明 //... }
聲明一個變量時,就能夠在變量名後面跟 :
冒號來聲明變量的數據類型,若是賦值給變量聲明的數據類型以外的類型,編譯器會有錯誤提示;函數的返回值的類型聲明方式相似。
若是某個變量的取值能夠是任意類型的,那麼能夠聲明爲 any:
let variable:any = 1; //聲明可爲任意類型的變量 variable = true;//此時賦值其餘類型都不會報錯
若是某個變量取值只能是某幾個類型之間,能夠用 |
聲明容許的多個類型:
let numStr:number|string = 1; //聲明可爲string或number類型變量 numStr = "str"; numStr = true;// 報錯
若是變量是個數組:
let numArr:number[] = [1, 2]; //聲明純數字數組,若是某個元素不是數字類型,會報錯 let anyArr:any[] = [1, "tr", true]; //數組元素類型不限制 let numStrArr:(number|string)[] = [1, "tr", 2, 4]; // 數組元素類型限制在只能是 number 和 string
若是變量是個對象:
let obj:object = {};
但這一般沒有什麼意義,由於函數,數組,自定義對象都屬於 object,因此能夠更具體點,好比聲明變量是個函數:
let fun:(a:number)=>string = function (a:number):string { //聲明函數類型的變量 return ""; }
聲明 fun 變量是一個函數類型時,還須要將函數的結構聲明出來,也就是函數參數,參數類型,返回值類型,經過 ES6 的箭頭函數語法來聲明。
但賦值的時候,賦值的函數參數類型,返回值類型能夠不顯示聲明,由於編譯器能夠根據函數體來自動推斷,好比:
let fun:(a:number)=>string = function (a) { return ""; }
若是變量是某個自定義的對象:
class Dog { name:string; age:number = 0; } let dog:Dog = new Dog(); //聲明自定義對象類型的變量
定義類的語法後面介紹,在 JavaScript 裏,鴨式辯型的編程理念比較適用,也就說,判斷某個對象是否歸屬於某個類時,並非看這個對象是不是從這個類建立出來的,而是看這個對象是否具備類的特徵,即類中聲明的屬性,對象是否擁有,有,則認爲這個對象是屬於這個類的。如:
let dog:Dog = {name:"dog", age:123}; //能夠賦值成功,由於對象直接量具備 Dog 類中的屬性 let dog1:Dog = {name:"dog", age:1, sex:"male"}; //錯誤,多了個 sex let dog2:Dog = {name:"dog"}; //錯誤,少了個 age let dog3:Dog = {name:"dog", age:"12"}; //錯誤,age 類型不同
以上例子中:
let dog1:Dog = {name:"dog", age:1, sex:"male"};
從鴨式辯型角度來講,這個應該是要能夠賦值成功的,由於目標對象擁有類指定的特徵行爲了,TypeScript 以爲額外多出的屬性可能會形成問題,因此會給一個錯誤提示。
針對這種由於額外多出的屬性檢查而報錯的狀況,若是想要繞開這個限制,有幾種方法:
let dog1:Dog = <Dog>{name:"dog", age:1, sex:"male"}; let dog1:Dog = {name:"dog", age:1, sex:"male"} as Dog;
類型斷言就是相似 Java 裏的強制類型轉換概念,經過 <>
尖括號或者 as
關鍵字,能夠告訴編譯器這個值的數據類型。
類型斷言經常使用於開發者明確知道某個變量的數據類型的狀況下。
若是賦值語句右側是一個變量,而不是對象直接量的話,那麼只會檢查變量是否擁有賦值語句左側所聲明的類型的特徵,而不會去檢查變量額外多出來的屬性,如:
let o = {name:"dog", age:1, sex:"male"}; let dog1:Dog = o;
這種方式是最佳的方式,官網中對它的描述是字符串索引簽名,但我以爲這個描述很難理解,並且看它實現的方式,有些相似於 ES6 中的函數的剩餘參數的處理,因此我乾脆本身給它描述成剩餘屬性的說法了。
方式是這樣的,在類中定義一個用於存儲其餘沒有聲明的屬性數組:
class Dog { name:string; age:number = 0; [propName:string]:any; }
最後一行 [propName:string]:any
就表示:具備 Dog 特徵的對象除了須要包含 name 和 age 屬性外,還能夠擁有其餘任何類型的屬性。因此:
let dog1:Dog = {name:"dog", age:1, sex:"male", s:true};
這樣就是被容許的了。
固然,這三種能夠繞開多餘屬性的檢查手段,應該適場景而使用,不能濫用,由於,大部分狀況下,當 TypeScript 檢查出你賦值的對象多了某個額外屬性時,程序會所以而出問題的概念是比較大的。
鴨式辯型在 TypeScript 裏更經常使用的是利用接口來實現,後續介紹。
鴨式辯型其實嚴格點來說就是對具備結構的值進行類型檢查,而具備結構的值也就是對象了,因此對對象的類型檢查,其實也就是在對對象進行類別劃分。
既然是類別劃分,那麼不一樣類別固然須要有個標識來相互區分,在 TypeScript 裏,接口的做用之一也就是這個,做爲不一樣對象類別劃分的依據。
好比:
interface Dog { name:string; age:number; eat():any; }
上述就是定義了,對象若是擁有 name, age 屬性和 eat 行爲,那麼就能夠將這個對象歸類爲 Dog,即便建立這個對象並無從實現了 Dog 接口的類上實例化,如:
let dog:Dog = { name: "小黑", age:1, eat: function () { //... } }
上述代碼聲明瞭一個 Dog 類型的變量,那麼什麼對象纔算是 Dog 類型,只要擁有 Dog 中聲明的屬性和行爲就認爲這個對象是 Dog,這就是鴨式辯型。(屬性和行爲是 Java 裏面向對象常說的概念,屬性對應變量,行爲對應方法,在 JavaScript 裏變量和方法都屬於對象的屬性,但既然 TypeScript 也有相似 Java 的接口和類語法,因此這裏我習慣以 Java 那邊的說法來描述了,反正能理解就行)
固然,也能夠經過定義一個 Dog 類來做爲變量的類型聲明,但接口相比於類的好處在於,接口裏只能有定義,一個接口裏具備哪些屬性和行爲一目瞭然,而類中經常攜帶各類邏輯。
既然接口做用之一是用來定義對象的類別特徵,那麼,它還有不少其餘的用法,好比:
interface Dog { name:string; age:number; eat:()=>any; master?:string; //狗的主人屬性,無關緊要 readonly variety:string; //狗的品種,一輩子下來就固定了 } let dog1:Dog = {name:"dog1", age:1, eat:()=>"", variety:"柯基"}; dog1.age = 2; dog1.variety = "中華犬";//報錯,variety聲明時就被固定了,沒法更改 let dog2:Dog = {name:"dog2", age:1, eat:()=>"", master: "me",variety:"柯基"};
在接口裏聲明屬性時,可用 ?
問號表示該屬性可有也可沒有,可用 readonly 來表示該屬性爲只讀屬性,那麼在定義時初始化後就不能再被賦值。
?
問號用來聲明該項無關緊要不只能夠用於在定義接口的屬性時使用,還能夠用於聲明函數參數時使用。
在類型聲明一節中說過,聲明一個變量的類型時,也能夠聲明爲函數類型,而函數本質上也是對象,因此,若是有需求是須要區分多個不一樣的函數是否屬於同一個類別的函數時,也能夠用接口來實現,如:
interface Func { (name:string):boolean; } let func:Func = function (name) { return true; }
這種使用接口的方式稱爲聲明函數類型的接口,能夠簡單的理解爲,爲 Func 類型的變量定義了 ()
運算符,需傳入指定類型參數和返回指定類型的值。
若是想讓某個類型既能夠當作函數被調用,又能夠做爲對象,擁有某些屬性行爲,那麼能夠結合上述聲明函數類型的接口方式和正常的接口定義屬性行爲方式一塊兒使用。
當對象或函數做爲函數參數時,經過接口來定義這些參數的類型,就特別有用,這樣能夠控制函數調用時傳入了預期類型的數據,若是類型不一致時,編譯階段就會報錯。
固然,接口除了用來在鴨式辯型中做爲值類型的區分外,也能夠像 Java 裏的接口同樣,定義一些行爲規範,強制實現該接口的類的行爲,如:
interface Dog { name:string; age:number; eat:()=>any; master?:string; //狗的主人屬性,無關緊要 readonly variety:string; //狗的品種,一輩子下來就固定了 } class ChinaDog implements Dog{ age: number = 0; eat: () => any; master: string; name: string; readonly variety: string = "中華犬"; }
ChinaDog 實現了 Dog 接口,那麼就必須實現該接口所定義的屬性行爲,因此,ChinaDog 建立的對象明顯就屬於 Dog:
let dog3:Dog = new ChinaDog();
除了這些基本用法外,TypeScript 的接口還有其餘不少用法,好比,定義構造函數:
interface Dog { new (name:string): Dog; }
再好比接口的繼承:接口可繼承自接口,也可繼承自類,繼承的時候,可同時繼承多個等。
更多高級用法,等有具體的使用場景,碰到的時候再深刻去學習,先了解到這程度吧。
習慣 Java 代碼後,首次接觸 ES5 多多少少會很不適應,由於 ES5 中都是基於原型的繼承,沒有 class 概念,自定義個對象都是寫構造函數,寫 prototype。
後來 ES6 中新增了 class 語法糖,能夠相似 Java 同樣經過 class 自定義對象,但仍是有不少區別,好比,ES6 中的 class 語法糖,就沒法聲明成員變量,成員變量只能在構造函數內定義和初始化;並且,也沒有權限控制、也沒有抽象方法機制、也不能定義靜態變量等等。
然而,這一切問題,在 TypeScript 中都獲得瞭解決,TypeScript 的 class 語法基本跟 Java 同樣,有 Java 基礎的,學習 TypeScript 的 class 語法會很輕鬆。
看個例子:
abstract class Animal { //定義抽象類 age:number; protected abstract eat():void; //抽象方法,權限修飾符 } class Dog extends Animal{ //類的繼承 public static readonly TAG:string = "Dog"; //定義靜態常量 public name:string; private isDog:boolean = true; //定義私有變量 constructor(name:string) { super(); this.name = name; this.age = 0; } protected eat:()=>any = function () { this.isDog; } get age():number { //將 age 定義爲存取器屬性 return this.age; } set age(age:number) { //將 age 定義爲存取器屬性 if (age > 0) { this.age = age; } else { age = 0; } } } let dog:Dog = new Dog("小黑");
大概有幾個地方跟 Java 有些許小差異:
其他方面,無論是權限的控制、繼承的寫法、成員變量的定義或初始化、抽象類的定義、基本上都跟 Java 的語法差很少。
因此說 TypeScript 的 class 語法比 ES6 的 class 語法糖要更強大。
還有不少細節的方面,好比在構造函數的參數前面加上權限修飾符,此時這個參數就會被當作成員變量來處理,能夠節省掉賦值的操做;
好比在 TypeScript 裏,類還能夠當作接口來使用。更多用法後續有深刻再慢慢記錄。
Java 裏在操做實體數據時,常常會須要用到泛型,但 JavaScript 自己並不支持泛型,不過 TypeScript 支持,好比:
interface Adapter<T> { data:T; } class StringAdapter implements Adapter<string>{ data: string; } function f1<Y extends Animal>(arg:Y):Y { return; } f1(new Dog("小黑"));
Dog 和 Animal 使用的是上個小節中的代碼。
用法基本跟 Java 相似,函數泛型、類泛型、泛型約束等。
JavaScript 跟 Java 很不同的一點就是,Java 有 class 機制,不一樣文件都須要有一個 public class,每一個文件只是用於描述一個類的屬性和行爲,類中的變量不會影響其餘文件內的變量,即便有同名類,只要類文件路徑不一致便可。
但 JavaScript 全部的 js 文件都是運行在全局空間內,所以若是不在函數內定義的變量都屬於全局變量,即便分散在多份不一樣文件內,這就很容易形成變量衝突。
因此也纔有那麼多模塊化規範的技術。
雖然 TypeScript 的 class 語法很相似於 Java,但 TypeScript 最終仍舊是要轉換成 JavaScript 語言的,所以即便用 TypeScript 來寫 class,只要有出現同名類,那麼即便在不一樣文件內,仍舊會形成變量衝突。
解決這個問題的話,TypeScript 也支持了模塊化的語法。
並且,TypeScript 模塊化語法有一個好處是,你只需掌握 TypeScript 的模塊化語法便可,編譯階段能夠根據配置轉換成 commonJs, amd, cmd, es6 等不一樣模塊化規範的實現。
TypeScript 的語法跟 ES6 中的模塊語法很相似,只要 ts 文件內出現 import 或 export,該文件就會被當作模塊文件來處理,即整個文件內的代碼都運行在模塊做用域內,而不是全局空間內。
//module.ts export interface StringValidator { isAcceptable(s: string): boolean; } export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } class AarCodeValidator implements StringValidator { isAcceptable(s: string) { //... } } export { AarCodeValidator };
export 的語法基本跟 ES6 中 export 的用法同樣。
若是其餘模塊須要使用該模塊的相關接口:
import { ZipCodeValidator } from "./module"; let myValidator = new ZipCodeValidator();
若是想描述非 TypeScript 編寫的類庫的類型,咱們須要聲明類庫所暴露出的API。一般須要編寫 .d.ts 聲明文件,相似於 C++ 中的 .h 文件。
.d.ts 聲明文件的編寫,以及引用時須要用到三斜槓指令:
/// <reference path="./m2.d.ts"/>
這部份內容我還沒理解清楚,後續碰到實際使用掌握後再來講說。
命名空間與模塊的區別在於,模塊會涉及到 import 或 export,而命名空間純粹就是當前 ts 文件內的代碼不想運行在全局命名空間內,因此能夠經過 命名空間的語法,讓其運行在指定的命名空間內,防止污染全局變量。
語法:
namespace Validation { //... }
本篇只講了 TypeScript 的一些基礎語法,還有其餘更多知識點,好比引入三方不是用 TypeScript 寫的庫時須要編寫的 .d.ts 聲明文件,好比編譯配置文件的各類配置項,好比枚舉,更多更多的內容,請參考開頭聲明部分給出的 TypeScript 中文網鏈接。
你們好,我是 dasu,歡迎關注個人公衆號(dasuAndroidTv),公衆號中有個人聯繫方式,歡迎有事沒事來嘮嗑一下,若是你以爲本篇內容有幫助到你,能夠轉載但記得要關注,要標明原文哦,謝謝支持~