作者:老余捞鱼
原创不易,转载请标明出处及原作者。
写在前面的话:
编写一流的 JavaScript 代码并非在于对最新技术趋势的盲目追逐,也并非要去构建繁杂的解决方案。本文给出了若干实用的技巧,它助力我写出了更优质的代码。倘若您产生了像“其他开发人员能否轻松读懂此代码?”“此代码能否正确地进行扩展,抑或容易被错误使用?”以及“这是否属于正确的模式?”这般的疑问,本文或许能够给予您一些颇具价值的启示。那么,让我们即刻启程!
#1:使用 Lodash
对于那些不知道的人来说,Lodash是一个包含多功能工具的 JS 库,可以简化编码任务并使日常挑战更容易处理。从代码角度来看,它相当于瑞士军刀。如果你还没有将它纳入你的代码库,你应该这样做。Npm趋势不会撒谎:它的下载量是React 的两倍,并且还在继续攀升。
它可以轻松处理日常编码挑战,改进您的代码。以下是它如何改进您的代码的一些示例:
提高可读性
例如,假设您要转换如下对象:
const obj = {
required: false,
custom: true,
essential: true,
default: false,
};
放入只有具有真值的键的数组中:[‘custom’, ‘essential']
。仅使用 JS 工具,您需要想出类似以下内容的内容:
const truthyKeys = Object
. keys (obj)
. filter ( key => obj[key] === true );
当然,普通的 JavaScript 版本也可以工作,但与 Lodash 的方法相比,它的可读性稍差一些:
const keys = _.chain (obj) .pickBy (
Boolean ) .keys (
) . value ( ) ;
它提供了很多有用的方法
好吧,你可能不太相信。那么合并嵌套对象怎么样?
使用 JS:
const merged = {
...obj1,
...obj2,
nested: {
...obj1.nested,
...obj2.nested,
},
};
使用Lodash:
const merged = _.merge({}, obj1, obj2);
它不仅用于合并。您还可以使用 ;等来.flattenDeep
展平深层嵌套的数组或按唯一项过滤数组。.uniqBy
开箱即用的防抖/节流
防抖或节流是常见的做法,但如果您不使用 Lodash,您可能正在编写自己的函数或使用第三方软件包。既然 Lodash 有现成的依赖项,为什么还要使用新的依赖项呢?
使用 JS:
// 导入或创建自己的:
function debounce ( func, wait ) {
let timeout;
return function ( ...args ) {
clearTimeout (timeout);
timeout = setTimeout ( () => func.apply ( this , args), wait);
};
}
const debouncedFunc = debounce (func, wait)
使用Lodash:
const debouncedFunc = _.debounce ( func , wait);
#2:尽量避免使用 Switch Case
switch
这些情况可能很有用,但它们也有一些缺点,使得它们与其他解决方案相比不太理想:
- 它们很快就会变得冗长且难以阅读,
- 容易出现诸如缺少
break
语句之类的错误,从而导致意外执行多个案例的失败错误, - 他们比其他替代方案花费了更多行来编写相同的逻辑,
- 默认促进缺乏关注点分离(如果忘记分离,代码将在后续情况下继续失败)。
那么,有什么更好的选择吗?使用对象或映射将数据和逻辑分开:
// instead of:
const retrieveStatus (statusCode) => {
return switch (statusCode) {
case 1:
return 'Pending';
case 2:
return 'In Progress';
case 3:
return 'Completed';
case 4:
// Missing break statement, so default it is
default:
return 'Unknown';
}
}
// do this:
const statuses = {
1: 'Pending',
2: 'In Progress',
3: 'Completed',
4: 'Failed',
5: 'Unknown'
};
const retrieveStatus = (statusCode) => _.get(statuses, statusCode, statuses.5);
这种方法可让您的代码更简洁、更易读,并将逻辑集中在一个地方。这样,您就无需在一个函数中浏览大量案例或条件。如果您需要查看或修改逻辑,可以检查保存备选方案的对象或映射。它更易于阅读且扩展性更好。
#3:避免if/else if
声明
从上一点继续说,您还应该避免使用if/else if
块。
使用if/else if
语句很快就会变得繁琐且难以阅读,尤其是在处理许多条件时。如果您编写了多个if/else if
块,请重新构造它们以使用对象或映射,就像我们对案例所做的那样switch
。如您所见,object + getter 函数统治了它们。
例如,而不是:
function getStatusDescription(statusCode) {
if (statusCode === 1) {
return 'Pending';
} else if (statusCode === 2) {
return 'In Progress';
} else if (statusCode === 3) {
return 'Completed';
} else if (statusCode === 4) {
return 'Failed';
} else {
return 'Unknown';
}
}
使用:
const statuses = {
1: 'Pending',
2: 'In Progress',
3: 'Completed',
4: 'Failed',
5: 'Unknown'
};
const withdrawStatus = (statusCode) => _.get (statuses, statusCode , statuses .5 );
是的,它与 switch 语句的块相同。
#4:不要嵌套 Ifs — Use 扁平结构
嵌套if
语句会很快使你的代码变得一团糟。不要将if
块堆叠在一起,而要使用扁平结构。识别此模式的一个简单方法是检查你的代码是否看起来像箭头。你可以通过查看缩进来发现这一点:每次有新的块时,代码看起来都会像箭头。每次你有箭头代码时,你都应该重构它。让我们来看一个例子:
const statusHandlers = {
pending: (order) => {
// do something
},
shipped: (order) => {
// do something
},
default: (order) => {
// do something
},
};
const paymentHandlers = {
creditCard: (order) => {
// do something
},
paypal: (order) => {
// do something
},
default: (order) => {
// do something
},
};
function processOrder(order) {
// typeguards and validations always at the top
if (!order) {
return handleInvalidOrder();
}
const { status, paymentMethod } = order;
const handleStatus = _.get(statusHandlers, status) || statusHandlers.default;
const handlePayment = _.get(paymentHandlers, paymentMethod) || paymentHandlers.default;
try {
handleStatus(order);
handlePayment(order);
} catch (e) {
return `Order failed. Reason: ${e.message}`
}
return "Order processed";
}
看到扁平结构如何让事情变得更清晰了吗?此外,将其分解成更小的部分使测试变得轻而易举,并允许您将代码组织到不同的文件中,从而提高可读性并使一切保持整洁。
此外,object + getter 函数再次让我们的生活更加轻松。这一次,getter 返回一个完整的函数 — 处理程序 — 而不是一个简单的原语。
#5 不要使用 let
自 ES6 以来,引入了let
和const
来替代var
变量定义。说实话,这很棒。但是,lets
应该很少使用。同样,所有递归函数都可以用循环和显式堆栈来实现,所有let
声明都可以重新定义为const
。
让我们看一个例子。以下函数处理表中的排序。它接收应按其排序的字段和方向。当它收到一个新元素时,如果其fieldName
不同,它应该将排序重置direction
为后代,无论它在该字段上收到什么有效载荷。但如果相同fieldname
,则应使用新的排序方向:
type Direction: 'asc' | 'desc'
type SortOptions {
fieldName: string;
direction: Direction;
}
const sortList = (
{ newOptions, currentOptions }: { newOptions: SortOptions, currentOptions: SortOptions }
): SortOptions => {
let sortingDirection: Direction;
if (currentOptions.fieldName !== newOptions.fieldName) {
sortingDirection = 'desc';
} else {
sortingDirection = newOptions.direction;
}
return {
fieldName: newOptions.fieldName,
direction: sortingDirection,
};
};
没啥不好,对吧?但是如果我们删除声明let
并直接使用if
/else
呢?
type Direction = 'asc' | 'desc';
interface SortOptions {
fieldName: string;
direction: Direction;
}
const sortList = (
{ newOptions, currentOptions }: { newOptions: SortOptions, currentOptions: SortOptions }
): SortOptions => {
const newDirection = currentFieldName !== newFieldName ? 'desc' : newOptions.direction
return {
fieldName: newOptions.fieldName,
direction: newDirection,
};
};
有时,您可以使用其他语法(例如三元运算符)重写它。在其他情况下,您需要将其外包给另一个函数。但这不是很棒吗?如果您将其拆分为另一个函数,您的代码将更好地遵循 SRP 原则,并且变得更易于测试、阅读和维护。
#6 不要使用 else,而要尽早返回
使用else
没什么问题,但尽早returns
可以使您的代码更清晰、更易读。尽早返回有助于避免嵌套条件并使其更易于理解,从而降低代码的复杂性。让我们看一个例子:
const calculateResult = (input: number): number => {
let result: number;
if (input > 10) {
result = input * 2;
} else {
result = input + 5;
}
return result;
};
使用提前返回后:
const calculateResult = (input: number): number => {
if (input > 10) {
return input * 2;
}
return input + 5;
};
没什么问题早期返回允许您提前处理边缘情况或特殊条件,从而使主要逻辑更简洁、更集中。
Wrapping up
每当你看到:
switch
cases,if
/else
if blocks,let
declarations,else
blocks,
它表示代码可以重构为更简单、更小的函数。保持代码整洁;这样可以帮助队友更好地理解你的代码。另外,别忘了使用 Lodash。它非常有用,可以避免手写大量“辅助”函数。
Happy coding!
本文内容仅仅是技术探讨和学习,并不构成任何投资建议。
转发请注明原作者和出处。