JavaScript 寫起來有很大彈性,但如何改善自己的 JavaScript 品質呢?以下是一份實用的參考指南。
改善 JS 的第一個技巧就是,不要寫 JS。寫 TypeScript 吧。它是微軟寫的一個語言,算是 JS 的母集合(一般的 JS 可以直接在 TS 裡面跑)。TS 在 JS 之上添加了一個全面的型別系統。長期下來,整個前端生態系,幾乎都支援 TS 語言了。下面介紹一下 TS 的優點。
TypeScript 可以確保「型別安全」
型別安全代表編譯器驗證了所有型別是否以「合法」的方式被使用。換句話說,如果你創建一個接受「數字」的函數 foo
:
function foo(someNum: number): number {
return someNum + 5;
}
該「foo」函數呼叫時要傳一個數字:
正確用法
console.log(foo(2)); // prints "7"
錯誤用法
console.log(foo("two")); // invalid TS code
除了需要花時間加型別以外,沒有其他缺點了。好處則是顯而易見。型別安全針對常見錯誤提供了額外預防,這對於像 JS 這樣鬆散的語言來說,是一件好事。
TypeScript 的型別系統,讓你能重構大型應用程式
重構大型 JS 應用程式是一場噩夢。主要是因為它不會檢查函數簽名。也就是說,JS函數隨便呼叫,編譯器都不會先報錯。舉個例,如果我有一個函數「myAPI」,由 1000 個不同的服務使用:
function myAPI(someNum, someString) {
if (someNum > 0) {
leakCredentials();
} else {
console.log(someString);
}
}
我稍微更改了呼叫簽名:
function myAPI(someString, someNum) {
if (someNum > 0) {
leakCredentials();
} else {
console.log(someString);
}
}
我必須自已 100% 去確認,使用此功能的1000 個地方,都有正確更新用法。漏掉的地方,在執行時就會壞掉。在 TS 中相同的情境如下:
before
function myAPITS(someNum: number, someString: string) { ... }
after
function myAPITS(someString: string, someNum: number) { ... }
如您所見,「myAPITS」函數跟在 JavaScript 中一樣修改。但是,這段程式碼無法順利產出 JavaScript,而是會出現無效 TypeScript 的錯誤提示,因為用到它的1000個地方,現在型別就出錯了。正是因為「型別安全」,這 1000 個案例會阻止編譯。
TypeScript 使團隊溝通架構更容易
正確設定 TS 後,要先定義介面跟類型,不然很難寫程式碼。這也順便提供了一種簡潔、可溝通的架構提案方法。例如,如果我想跟後端提出新的「請求」類型,可以使用 TS 將以下內容提交給團隊。
interface BasicRequest {
body: Buffer;
headers: { [header: string]: string | string[] | undefined; };
secret: Shhh;
}
雖然也是要寫一點程式碼,但可以先提供以上內容作為初步想法、取得回饋,而不用一次設計完功能。像這樣強迫開發者先定義介面跟 API,能讓程式碼品質更好。
總體而言,TS 已經發展成為一種成熟且更可預測的 vanilla JS 替代品。當然還是要熟 JS,但我現在大多數新專案都是直接寫 TS。
JavaScript是世界上最流行的程式設計語言之一。你可能會以為這語言已經被百萬開發者摸透了,其實不是。很多新功能是近期才加上去的。
async
與 await
長期以來,非同步、事件驅動的 callback 是 JS 開發中不可避免的:
傳統 callback
makeHttpRequest('google.com', function (err, result) {
if (err) {
console.log('Oh boy, an error');
} else {
console.log(result);
}
});
上述程式碼會有越寫越多層的問題。JS 添加了一個新概念叫 Promises,可以避免嵌套問題。
Promises
makeHttpRequest('google.com').then(function (result) {
console.log(result);
}).catch(function (err) {
console.log('Oh boy, an error');
});
與 callback 相比,Promise 的最大優勢是可讀性和可鏈性。
雖然 Promise 很棒,但它們仍然有一些不足之處。寫起來感覺有點怪。為了解決這個問題,ECMAScript 委員會添加一種使用 promise 的新方法,async
和 await
:
async
與 await
try {
const result = await makeHttpRequest('google.com');
console.log(result);
} catch (err) {
console.log('Oh boy, an error');
}
需要注意的是,await
的內容必須先被宣告為 async
:
以前面 makeHttpRequest 舉例
async function makeHttpRequest(url) {
// ...
}
也可以直接 await
一個 Promise,因為 async
函數實際上只是 Promise 比較花哨的包裝寫法。這也意味著,async/await
代碼和 Promise 代碼在功能上是等價的。因此,請大方使用 async/await
吧。
let
and const
以前大家寫 JS 都只能用 var
。var
的變數作用域有一些獨特規則,一直讓人很困惑。從 ES6 開始,有了 const
跟 let
可以替代使用,幾乎不用再寫 var
了。
至於何時要用 const 何時要用 let 呢?永遠從 const 先開始使用。const 因為不可修改、更可預期,會讓程式碼品質更好。使用 let 的場景比較少,我寫 const 的頻率比 let 高 20 倍左右。
箭頭 =>
函數
箭頭函數是在JS中宣告匿名函數的簡潔方法。通常作為 callback 或 event hook 傳遞。
傳統的匿名 function
someMethod(1, function () { // has no name
console.log('called');
});
在大多數情況下,這種風格沒有任何“不對”。但它的變數作用域很“有趣”,常導致許多意想不到的錯誤。有了箭頭函數之後,就沒這問題了。
匿名箭頭函數
someMethod(1, () => { // has no name
console.log('called');
});
除了更簡潔之外,箭頭函數還具有更實用的變數範圍界定。箭頭函數從定義它們的作用域繼承「this」。
在某些情況下,箭頭函數甚至可以更簡潔:
const added = [0, 1, 2, 3, 4].map((item) => item + 1);
console.log(added) // prints "[1, 2, 3, 4, 5]"
箭頭函數可以包括隱式的「return」語句。就不用寫大括弧、分號。
不過,這跟“var”被完全取代不同。傳統匿名函數仍然有需要的地方,例如類別方法定義。話雖如此,如果你總是預設使用箭頭函數,程式碼錯誤會少很多。
展開運算子 ...
提取一個物件的鍵/值對,並將它們添加為另一個物件的子物件,是一種很常見的場景。從歷史上看,有幾種方法可以實現,但通通都很笨拙:
const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
const merged = Object.assign({}, obj1, obj2);
console.log(merged) // prints { dog: 'woof', cat: 'meow' }
這寫法以前很常見,也很囉唆。多虧了「展開運算子」,再也不需要那樣寫了:
const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' }
最棒的是,與陣列也可無縫接軌:
const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([ ...arr1, ...arr2 ]); // prints [1, 2, 3, 4]
它可能不是近期 JS 中最重要的功能,但它是我的最愛之一。
範本文字(範本字串)
字串處理太常見了,程式語言要能處理範本字串,才夠好用。處理動態內容、多行文字時,都會需要它:
const name = 'Ryland';
const helloString =
`Hello
${name}`;
我認為程式碼不言自明。看起來好多了。
物件解構
物件解構是一種從資料集合(物件、陣列等)中提取值,而無需反覆運算數據或顯式訪問其鍵的方法:
老方法
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict.dog, myDict.cat);
新方法
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
const { dog, cat } = myDict;
animalParty(dog, cat);
不只這樣。您還可以在函數的簽名中定義解構:
解構範例二
function animalParty({ dog, cat }) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict);
它也適用於陣列:
解構範例三
[a, b] = [10, 20];
console.log(a); // prints 10
以上,希望對您有幫助!
很好的文章~!