本文深入探讨如何编写高质量的 JavaScript 代码,从易读性、高性能和健壮性三个维度提供实用建议。内容涵盖统一代码格式、消除魔术数字、单一职责、减少嵌套、算法优化、内置方法使用、作用域链管理、缓存机制、新语法应用、纯函数设计以及逻辑关注点整合等核心技巧,帮助开发者提升代码质量与可维护性。
近期,“人类高质量男性”相关视频在网络上引发热议,相信不少读者都曾接触过。借此话题,本文将探讨何为“人类高质量代码”,这虽是一句玩笑,却引出了我们对代码质量的思考。
本文旨在分享个人在日常开发中总结的一些实用技巧,期望能起到抛砖引玉的作用,为读者提供些许启发与帮助。
接下来,我们将一起探讨如何编写出高质量的 JavaScript 代码。
一、易阅读的代码
首先强调,代码的首要读者是开发者自己或团队成员,良好的可读性是编写高质量代码的基础。为此,我们总结了四点具体操作建议。
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, "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()
除上述四点建议外,还有许多改善阅读体验的方法,例如添加有效注释、避免不同类型比较以及避免使用晦涩语法等。
二、高性能的代码
在软件开发中,代码性能直接影响用户体验,因此高质量代码必然具备高性能特征。以下总结四点具体操作建议。
提示:测试 JavaScript 平均耗时,可使用 console.time() 方法、JSBench.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
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、减少作用域链查找
作用域链是作用域规则的实现机制,通过它,变量可在其作用域内被访问,函数可在其作用域内被调用。作用域链是一个单向链表,链表上的每个节点代表执行上下文的变量对象(代码执行时即活动对象)。单向链表的头部(首个可访问节点)始终是当前正在执行的函数的变量对象(活动对象),而尾部始终指向全局活动对象。
若概念过于复杂,可参考以下图示。

该作用域链表现为 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 // 后面的循环没有意义,属于执行不必要的代码
}
}
再看一个计算“斐波那契数列”的案例。
// 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、使用新语法
许多新语法可弥补旧语法的缺陷,使代码更加健壮,以应对未来变化。
// 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,以及 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 等。
最后总结一下,如何编写高质量 JavaScript 代码:

到此这篇关于如何编写高质量 JavaScript 代码的文章就介绍到这了,更多相关编写高质量 JavaScript 代码内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!