如何編寫高質(zhì)量 JavaScript 代碼
前段時間有一個叫做“人類高質(zhì)量男性”的視頻火了,相信很多同學(xué)都刷到過。所以今天給大家分享下,什么叫做“人類高質(zhì)量代碼”,哈哈,開個玩笑。
其實分享的都是一些自己平時總結(jié)的小技巧,算是拋磚引玉吧,希望能給大家?guī)硪恍﹩l(fā)和幫助。
如何編寫出高質(zhì)量的 JavaScript 代碼?一起來學(xué)習(xí)下面文章內(nèi)容
一、易閱讀的代碼
首先說一下,代碼是寫給自己或團隊成員看的,良好的閱讀方式是編寫高質(zhì)量代碼的前提條件。這里總結(jié)了四點具體操作方式分享給大家。
1、統(tǒng)一代碼格式
不要一會這樣寫,一會那樣寫,盡量統(tǒng)一寫法,下面舉例。
// bad
function foo(x,y) {
return {
sum : x + y
};
}
function bar(m, n){
let ret = m*n
return ret;
}
// good
function foo(x, y) { // 適當(dāng)?shù)目崭窀糸_,一般符號前不添加空格,符號后添加空格
return {
sum: x + y, // 拖尾逗號是合法的,簡化了對象和數(shù)組添加或刪除元素
} // 省略結(jié)束分號,當(dāng)然需要知道如何規(guī)避風(fēng)險
}
function bar(m, n) {
let ret = m * n
return ret
}
人為去約定代碼格式,是很不方便的,所以可以借助一些工具進行自動格式轉(zhuǎn)換,如:prettier 插件(https://prettier.io/)。
2、去除魔術(shù)數(shù)字
魔術(shù)數(shù)字(magic number)是程式設(shè)計中所謂的直接寫在程式碼里的具體數(shù)值(如“10”“123”等以數(shù)字直接寫出的值)。雖然程式作者寫的時候自己能了解數(shù)值的意義,但對其他程式員而言,甚至作者本人經(jīng)過一段時間后,都會很難理解這個數(shù)值的用途。
// bad
setTimeout(blastOff, 86400000)
document.onkeydown = function (ev) {
if (ev.keyCode === 13) {
// todos
}
}
// good
const MILLISECONDS_IN_A_DAY = 86400000
const ENTER_KEY = 13
setTimeout(blastOff, MILLISECONDS_IN_A_DAY)
document.onkeydown = function (ev) {
if (ev.keyCode === ENTER_KEY) {
// todos
}
}
當(dāng)然還有魔術(shù)字符串也是像上面一樣去處理,上面代碼中的常量命名推薦采用下劃線命名的方式,其他如變量、函數(shù)等推薦用駝峰進行命名。
其實減少this的使用頻率也是一樣的道理,當(dāng)代碼中充斥著大量this的時候,我們往往很難知道它是誰,需要花費很多時間進行閱讀。
// bad
class Foo {
foo() {
this.number = 100
this.el.onclick = function () {
this.className = "active"
}
}
}
// good
class Foo {
foo() {
let context = this
context.number = 100
context.el.onclick = function () {
let el = this
el.className = "active"
}
}
}
3、單一功能原則
無論是編寫模塊、類、還是函數(shù)都應(yīng)該讓他們各自都只有單一的功能,不要讓他們做過多的事情,這樣閱讀起來會非常簡單,擴展起來也會非常靈活。
// bad
function copy(obj, deep) {
if (deep) {
// 深拷貝
} else {
// 淺拷貝
}
}
// good
function copy(obj) {
// 淺拷貝
}
function deepCopy(obj) {
// 深拷貝
}
4、減少嵌套層級
多層級的嵌套,如:條件嵌套、循環(huán)嵌套、回調(diào)嵌套等,對于代碼閱讀是非常不利的,所以應(yīng)盡量減少嵌套的層級。
像解決條件嵌套的問題,一般可采用衛(wèi)語句(guard clause)的方式提前返回,從而減少嵌套。
// bad
function foo() {
let result
if (isDead) {
result = deadAmount()
} else {
if (isRet) {
result = retAmount()
} else {
result = normalAmount()
}
}
return result
}
// good
function foo() {
if (isDead) {
return deadAmount()
}
if (isRet) {
return retAmount()
}
return normalAmount()
}
除了衛(wèi)語句外,通過還可以采用短路運算、條件運算符等進行條件語句的改寫。
// bad
function foo() {
if (isOk) {
todo()
}
let grade
if (isAdmin) {
grade = 1
} else {
grade = 0
}
}
// good
function foo() {
isOk && todo() // 短路運算
let grade = isAdmin ? 1 : 0 // 條件運算符
}
像解決回調(diào)嵌套的問題,一般可采用“async/await”方式進行改寫。
// bad
let fs = require("fs")
function init() {
fs.mkdir(root, (err) => {
fs.mkdir(path.join(root, "public", "stylesheets"), (err) => {
fs.writeFile(
path.join(root, "public", "stylesheets", "style.css"),
"",
function (err) {}
)
})
})
}
init()
// good
let fs = require("fs").promises
async function init() {
await fs.mkdir(root)
await fs.mkdir(path.join(root, "public", "stylesheets"))
await fs.writeFile(path.join(root, "public", "stylesheets", "style.css"), "")
}
init()
除了以上介紹的四點建議外,還有很多可以改善閱讀體驗的點,如:有效的注釋、避免不同類型的比較、避免生澀的語法等等。
二、高性能的代碼
在軟件開發(fā)中,代碼的性能高低會直接影響到產(chǎn)品的用戶體驗,所以高質(zhì)量的代碼必然是高性能的。這里總結(jié)了四點具體操作方式分享給大家。
提示:測試JavaScript平均耗時,可使用console.time()方法、JSBench.Me工具、performance工具等。
1、優(yōu)化算法
遞歸是一種常見的算法,下面是用遞歸實現(xiàn)的“求階乘”的操作。
// bad
function foo(n) {
if (n === 1) {
return 1
}
return n * foo(n - 1)
}
foo(100) // 平均耗時:0.47ms
// good
function foo(n, result = 1) {
if (n === 1) {
return result
}
return foo(n - 1, n * result) // 這里尾調(diào)用優(yōu)化
}
foo(100) // 平均耗時:0.09ms
“尾調(diào)用”是一種可以重用棧幀的內(nèi)存管理優(yōu)化機制,即外部函數(shù)的返回值是一個內(nèi)部函數(shù)的返回值。
2、使用內(nèi)置方法
很多功能都可以采用JavaScript內(nèi)置方法來解決,往往內(nèi)置方法的底層實現(xiàn)是最優(yōu)的,并且內(nèi)置方法可在解釋器中提前執(zhí)行,所以執(zhí)行效率非常高。
下面舉例為:獲取對象屬性和值的復(fù)合數(shù)組形式。
// bad
let data = {
username: "leo",
age: 20,
gender: "male",
}
let result = []
for (let attr in data) {
result.push([attr, data[attr]])
}
console.log(result)
// good
let data = {
username: "leo",
age: 20,
gender: "male",
}
let result = Object.entries(data)
console.log(result)
3、減少作用域鏈查找
作用域鏈?zhǔn)亲饔糜蛞?guī)則的實現(xiàn),通過作用域鏈的實現(xiàn),變量在它的作用域內(nèi)可被訪問,函數(shù)在它的作用域內(nèi)可被調(diào)用。作用域鏈?zhǔn)且粋€只能單向訪問的鏈表,這個鏈表上的每個節(jié)點就是執(zhí)行上下文的變量對象(代碼執(zhí)行時就是活動對象),單向鏈表的頭部(可被第一個訪問的節(jié)點)始終都是當(dāng)前正在被調(diào)用執(zhí)行的函數(shù)的變量對象(活動對象),尾部始終是全局活動對象。
概念太復(fù)雜的話, 看下面這樣一張圖。

作用域鏈這個鏈表就是 3(頭部:bar) -> 2(foo) -> 1(尾部:全局),所以查找變量的時候,應(yīng)盡量在頭部完成獲取,這樣就可以節(jié)省性能,具體對比如下。
// bad
function foo() {
$("li").click(function () { // 全局查找一次
$("li").hide() // 再次全局查找一次
$(this).show()
})
}
// good
function foo() {
let $li = $("li") // 減少下面$li的作用域查找層級
$li.click(function () {
$li.hide()
$(this).show()
})
}
除了減少作用域鏈查找外,減少對象屬性的查找也是一樣的道理。
// bad
function isNull(arg) {
return Object.prototype.toString.call(arg) === "[object Null]"
}
function isFunction(arg) {
return Object.prototype.toString.call(arg) === "[object Function]"
}
// good
let toString = Object.prototype.toString
function isNull(arg) {
return toString.call(arg) === "[object Null]"
}
function isFunction(arg) {
return toString.call(arg) === "[object Function]"
}
4、避免做重復(fù)的代碼
有時候編寫程序時,會出現(xiàn)很多重復(fù)執(zhí)行的代碼,最好要避免做重復(fù)操作。先舉一個簡單的例子,通過循環(huán)找到第一個滿足條件元素的索引位置。
// bad
let index = 0
for (let i = 0, len = li.length; i < len; i++) {
if (li[i].dataset.switch === "on") {
index = i
}
}
// good
let index = 0
for (let i = 0, len = li.length; i < len; i++) {
if (li[i].dataset.switch === "on") {
index = i
break // 后面的循環(huán)沒有意義,屬于執(zhí)行不必要的代碼
}
}
再來看一個計算“斐波那契數(shù)列”的案例。
// bad
function foo(n) {
if (n < 3) {
return 1
}
return foo(n - 1) + foo(n - 2)
}
foo(40) // 平均耗時:1043ms
// good
let cache = {}
function foo(n) {
if (n < 3) {
return 1
}
if (!cache[n]) {
cache[n] = foo(n - 1) + foo(n - 2)
}
return cache[n]
}
foo(40) // 平均耗時:0.16ms
這里把遞歸執(zhí)行過的結(jié)果緩存到數(shù)組中,這樣接下來重復(fù)的代碼就可以直接讀取緩存中的數(shù)據(jù)了,從而大幅度提升性能。

畫叉號的部分就會走緩存,而不會重復(fù)執(zhí)行計算。
除了以上介紹的四點建議外,還有很多可以改善代碼性能的點,如:減少DOM操作、節(jié)流處理、事件委托等等。
三、健壯性的代碼
所謂健壯性的代碼,就是編寫出來的代碼,是可擴展、可維護、可測試的代碼。這里總結(jié)了四點具體操作方式分享給大家。
1、使用新語法
很多新語法可彌補之前語法的BUG,讓代碼更加健壯,應(yīng)對未來。
// bad var a = 1 isNaN(NaN) // true isNaN(undefined) // true // good let a = 1 Number.isNaN(NaN) // true Number.isNaN(undefined) // false
新語法還可以簡化之前的操作,讓代碼結(jié)構(gòu)更加清晰。
// bad
let user = { name: "james", age: 36 }
function foo() {
let arg = arguments
let name = user.name
let age = user.age
}
// good
let user = { name: "james", age: 36 }
function foo(...arg) { // 剩余參數(shù)
let { name, age } = user // 解構(gòu)賦值
}
2、隨時可擴展
由于產(chǎn)品需求總是會有新的變更,對軟件的可擴展能力提出了很高要求,所以健壯的代碼都是可以隨時做出調(diào)整的代碼。
// bad
function foo(animal) {
if (animal === "dog" || animal === "cat") {
// todos
}
}
function bar(name, age) {}
bar("james", 36)
// good
function foo(animal) {
const animals = ["dog", "cat", "hamster", "turtle"] // 可擴展匹配值
if (animals.includes(animal)) {
// todos
}
}
function bar(options) {} // 可擴展任意參數(shù)
bar({
gender: "male",
name: "james",
age: 36,
})
3、避免副作用
當(dāng)函數(shù)產(chǎn)生了除了“接收一個值并返回一個結(jié)果”之外的行為時,就產(chǎn)生了副作用。副作用不是說一定是有害的,但是如果在項目中沒有節(jié)制的引起副作用,代碼出錯的可能性會非常大。
建議盡量不要去修改全局變量或可變對象,通過參數(shù)和return完成需求。讓函數(shù)成為一種純函數(shù),這樣也可使代碼更容易被測試。
// bad
let fruits = "Apple Banana"
function splitFruits() {
fruits = fruits.split(" ")
}
function addItemToCart(cart, item) {
cart.push({ item, data: Date.now() })
}
// good
let fruits = "Apple Banana"
function splitFruits(fruits) {
return fruits.split(" ")
}
function addItemToCart(cart, item) {
return [...cart, { item, data: Date.now() }]
}
4、整合邏輯關(guān)注點
當(dāng)項目過于復(fù)雜的時候,經(jīng)常會把各種邏輯混在一起,對后續(xù)擴展非常不利,而且還影響對代碼的理解。所以盡量把相關(guān)的邏輯抽離到一起,進行集中式的管理。像React中的hooks,Vue3中的Composition API都是采用這樣的思想。
// bad
export default {
name: 'App',
data(){
return {
searchHot: [],
searchSuggest: [],
searchHistory: [],
},
mounted() {
// todo hot
// todo history
},
methods: {
handleSearchSuggest(){
// todo suggest
},
handleSearchHistory(){
// todo history
}
}
}
}
// good
export default {
name: "App",
setup() {
let { searchHot } = useSearchHot()
let { searchSuggest, handleSearchSuggest } = useSearchSuggest()
let { searchHistory, handleSearchHistory } = useSearchHistory()
return {
searchHot,
searchSuggest,
searchHistory,
handleSearchSuggest,
handleSearchHistory,
}
}
}
function useSearchHot() {
// todo hot
}
function useSearchSuggest() {
// todo suggest
}
function useSearchHistory() {
// todo history
}
除了以上介紹的四點建議外,還有很多可以改善代碼健壯性的點,如:異常處理、單元測試、使用TS替換JS等等。
最后總結(jié)一下,如何編寫高質(zhì)量JavaScript代碼:

到此這篇關(guān)于如何編寫高質(zhì)量 JavaScript 代碼的文章就介紹到這了,更多相關(guān)編寫高質(zhì)量 JavaScript 代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ECharts框架Sunburst拖拽功能實現(xiàn)方案詳解
這篇文章主要為大家介紹了ECharts框架Sunburst拖拽功能實現(xiàn)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
微信小程序網(wǎng)絡(luò)請求wx.request詳解及實例
這篇文章主要介紹了微信小程序網(wǎng)絡(luò)請求wx.request詳解及實例的相關(guān)資料,需要的朋友可以參考下2017-05-05
JavaScript選擇器函數(shù)querySelector和querySelectorAll
這篇文章主要介紹了?JavaScript選擇器函數(shù)querySelector和querySelectorAll,下面文章圍繞querySelector和querySelectorAll的相關(guān)資料展開詳細(xì)內(nèi)容,需要的朋友可以參考一下2021-11-11

