如何編寫高質量 JavaScript 程式碼
目錄
- 一、易閱讀的程式碼
- 1、統一程式碼格式
- 2、去除魔術數字
- 3、單一功能原則
- 二、高效能的程式碼
- 1、優化演算法
- 2、使用內建方法
- 3、減少作用域鏈查詢
- 4、避免做重複的程式碼
- 三、健壯性的程式碼
- 1、使用新語法
- 2、隨時可擴充套件
- 3、避免副作用
- 4、整合邏輯關注點
前段時間有一個叫做“人類高質量男性”的視訊火了,相信很多同學都刷到過。所以今天給大家分享下,什麼叫做“人類高質量程式碼”,哈哈,開個玩笑。
其實分享的都是一些自己平時總結的小技巧,算是拋磚引玉吧,希望能給大家帶來一些啟發和幫助。
如何編寫出高質量的 程式碼?一起來學習下面文章內容
一、易閱讀的程式碼
首先說一下,程式碼是寫給自己或團隊成員看的,良好的閱讀方式是編寫高質量程式碼的前提條件。這裡總結了四點具體操作方式分享給大家。
1、統一程式碼格式
不要一會這樣寫,一會那樣寫,儘量統一寫法,下面舉例。
// bad function foo(x,y) { return { sum : x + y }; } function bar(m,n){ let ret = m*n return ret; } // good function foo(x,y) { // 適當的空格隔開,一般符號前不新增空格,符號後新增空格 return { sum: x + y,// 拖尾逗號是合法的,簡化了物件和陣列新增或刪除元素 } // 省略結束分號,當然需要知道如何規避風險 } function bar(m,n) { let ret = m * n return ret }
人為去約定程式碼格式,是很不方便的,所以可以藉助一些工具進行自動格式轉換,如:prettier 外掛(https://prettier.io/)。
2、去除魔術數字
魔術數字(magic number
)是程式設計中所謂的直接寫在程式碼裡的具體數值(如“10”“123”等以數字直接寫出的值)。雖然程式作者寫的時候自己能瞭解數值的意義,但對其他程式員而言,甚至作者本人經過一段時間後,都會很難理解這個數值的用途。
// 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 } }
當然還有魔術字串也是像上面一樣去處理,上面程式碼中的常量命名推薦採用下劃線命名的方式,其他如變數、函式等推薦用駝峰進行命名。
其實減少this的使用頻率也是一樣的道理,當代碼中充斥著大量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、單一功能原則
無論是編寫模組、類、還是函式都應該讓他們各自都只有單一的功能,不要讓他們做過多的事情,這樣閱讀起來會非常簡單,擴充套件起來也會非常靈活。
// bad function copy(obj,deep) { if (deep) { // 深拷貝 } else { // 淺拷貝 } } // good function copy(obj) { // 淺拷貝 } function deepCopy(obj) { // 深拷貝 }
4、減少巢狀層級
多層級的巢狀,如:條件巢狀、迴圈巢狀、回撥巢狀等,對於程式碼閱讀是非常不利的,所以應儘量減少巢狀的層級。
像解決條件巢狀的問題,一般可採用衛語句(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() }
除了衛語句外,通過還可以採用短路運算、條件運算子等進行條件語句的改寫。
// 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 // 條件運算子 }
像解決回撥巢狀的問題,一般可採用“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,"stylesheets","style."),"",function (err) {} ) }) }) } init() // good let fs = require("fs").promises async function init() { await fs.mkdir(root) await fs.mkdir(path.join(root,"stylesheets")) await fs.writeFile(path.join(root,"style.css"),"") } init()
除了以上介紹的四點建議外,還有很多FzbxR可以改善閱讀體驗的點,如:有效的註釋、避免不同型別的比較、避免生澀的語法等等。
二、高效能的程式碼
在軟體開發中,程式碼的效能高低會直接影響到產品的使用者體驗,所以高質量的程式碼必然是高效能的。這裡總結了四點具體操作方式分享給大家。
提示:測試Script
平均耗時,可使用console.time()
方法、Bench.Me
工具、performance
工具等。
1、優化演算法
遞迴是一種常見的演算法,下面是用遞迴實現的“求階乘”的操作。
// 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) // 這裡尾呼叫優化 } foo(100) // 平均耗時:0.09ms
“尾呼叫”是一種可以重用棧幀的記憶體管理優化機制,即外部函式的返回值是一個內部函式的返回值。
2、使用內建方法
很多功能都可以採用JavaScript
內建方法來解決,往往內建方法的底層實現是最優的,並且內建方法可在直譯器中提前執行,所以執行效率非常高。
下面舉例為:獲取物件屬性和值的複合陣列形式。
// bad lethttp://www.cppcns.com data = { username: "leo",age: 20,gender: "male",} let result = [] for (let attr in data) { result.push([attr,www.cppcns.com data[attr]]) } console.log(result) // good let data = { username: "leo",} let result = Object.entries(data) console.log(result)
3、減少作用域鏈查詢
作用域鏈是作用域規則的實現,通過作用域鏈的實現,變數在它的作用域內可被訪問,函式在它的作用域內可被呼叫。作用域鏈是一個只能單向訪問的連結串列,這個連結串列上的每個節點就是執行上下文的變數物件(程式碼執行時就是活動物件),單向連結串列的頭部(可被第一個訪問的節點)始終都是當前正在被呼叫執行的函式的變數物件(活動物件),尾部始終是全域性活動物件。
概念太複雜的話, 看下面這樣一張圖。
作用域鏈這個連結串列就是 3(頭部:bar) -> 2(foo) -> 1(尾部:全域性),所以查詢變數的時候,應儘量在頭部完成獲取,這樣就可以節省效能,具體對比如下。
// 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、避免做重複的程式碼
有時候編寫程式時,會出現很多重複執行的程式碼,最好要避免做重複操作。先舉一個簡單的例子,通過迴圈找到第一個滿足條件元素的索引位置。
// 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 www.cppcns.com // 後面的迴圈沒有意義,屬於執行不必要的程式碼
}
}
再來看一個計算“斐波那契數列”的案例。
// 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
這裡把遞迴執行過的結果快取到陣列中,這樣接下來重複的程式碼就可以直接讀取快取中的資料了,從而大幅度提升效能。
畫叉號的部分就會走快取,而不會重複執行計算。
除了以上介紹的四點建議外,還有很多可以改善程式碼效能的點,如:減少DOM操作、節流處理、事件委託等等。
三、健壯性的程式碼
所謂健壯性的程式碼,就是編寫出來的程式碼,是可擴充套件、可維護、可測試的程式碼。這裡總結了四點具體操作方式分享給大家。
1、使用新語法
很多新語法可彌補之前語法的BUG,讓程式碼更加健壯,應對未來。
// bad var a = 1 isNaN(NaN) // true isNaN(undefined) // true // good let a = 1 Number.isNaN(NaN) // true Number.isNaN(undefined) // false
新語法還可以簡化之前的操作,讓程式碼結構更加清晰。
// 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) { // 剩餘引數 let { name,age } = user // 解構賦值 }
2、隨時可擴充套件
由於產品需求總是會有新的變更,對軟體的可擴充套件能力提出了很高要求,所以健壯的程式碼都是可以隨時做出調整的程式碼。
// 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) {} // 可擴充套件任意引數 bar({ gender: "male",name: "james",age: 36,})
3、避免副作用
當函式產生了除了“接收一個值並返回一個結果”之外的行為時,就產生了副作用。副作用不是說一定是有害的,但是如果在專案中沒有節制的引起副作用,程式碼出錯的可能性會非常大。
建議儘量不要去修改全域性變數或可變物件,通過引數和return
完成需求。讓函式成為一種純函式,這樣也可使程式碼更容易被測試。
// 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、整合邏輯關注點
當專案過於複雜的時候,經常會把各種邏輯混在一起,對後續擴充套件非常不利,而且還影響對程式碼的理解。所以儘量把相關的邏輯抽離到一起,進行集中式的管理。像React
中的hooks
,3
中的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等等。
最後總結一下,如何編寫高質量JavaScript
程式碼:
到此這篇關於如何編寫高質量 JavaScript 程式碼的文章就介紹到這了,更多相關編寫高質量 JavaScript 程式碼內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!