1、三元运算符
在实际工作中,我们常常需要对一个变量或表达式进行判断,根据不同的条件来执行不同的操作,通常情况下需要使用 if...else
语句。但是 if...else
语句过于冗长和繁琐,可以使用三元运算符来优化。
以实现一个状态机的代码为例,原始的代码可能长这样:
function handleEvent(event) {
if (currentState === STATE_INITIAL) {
// do something
} else if (currentState === STATE_SECONDARY) {
// do something
} else if (currentState === STATE_FINAL) {
// do something
}
}
三元运算符可以用于精简此类代码:
function handleEvent(event) {
currentState === STATE_INITIAL ?
// do something :
currentState === STATE_SECONDARY ?
// do something :
// do something
}
使用三元运算符可以使得代码更加简洁易懂,并且减少代码文件大小。同时,三元运算符可以提高代码可读性,逻辑也可以一眼看出来,从而更快速地找到问题所在和进行调试。在某些特定场景下,使用三元运算符还可以提高性能,因为有些引擎不能很好地理解 if...else
语句的含义,从而会降低其性能表现。
注意:虽然使用三元运算符来优化
if...else
语句能够大大提高代码的可读性、可维护性和性能表现,值得我们在实际工作中加以运用。不过需要注意的是,如果一个条件比较复杂或者拥有多个分支时,使用三元运算符会让代码难以阅读和理解,切忌不要滥用。
2、耍点小聪明
来看两个特定场景下的”小聪明技巧“~
2.1 短路求值(Short-circuit Evaluation)
短路求值是指在逻辑运算中,如果第一个表达式已经能够确定整个表达式的值,就不再计算后面的表达式。利用这个特性,可以通过 &&
和 ||
运算符来缩短 if...else
语句。例如:
let x = 10;
let result;
if (x > 5) {
result = "x is greater than 5";
} else {
result = "x is less than or equal to 5";
}
// 利用 && 运算符
let x = 10;
let result = x > 5 && "x is greater than 5" || "x is less than or equal to 5";
2.2 字符串拼接
先来看一类比较特殊的 if...else
语句:
function setAccType(accType) {
if (accType == "PLATINUM") {
return "Platinum Customer";
} else if (accType == "GOLD") {
return "Gold Customer";
} else if (accType == "SILVER") {
return "Silver Customer";
}
}
像这种条件和返回值有非常强的相关性的语句,通常我们就可以通过下面这种非常简洁的方式来处理,让多行秒变一行:
function setAccType(accType){
return accType[0] + accType.slice(1).toLowerCase() + ' Customer';
// or
return `${accType[0] + accType.slice(1).toLowerCase()} Customer`;
}
3、switch/case
switch/case
语句也是比较常用的技巧,来看下面这个例子:
if (status === 200) {
handleSuccess()
} else if (status === 401) {
handleUnauthorized()
} else if (status === 404) {
handleNotFound()
} else {
handleUnknownError()
}
像这样的,我们就可以通过 switch/case
来进行处理:
switch (status) {
case 200:
handleSuccess()
break
case 401:
handleUnauthorized()
break
case 404:
handleNotFound()
break
default:
handleUnknownError()
break
}
虽然多了几行代码,但避免了一次又一次的重复的全等检查,而且整体上更精简、直观。
4、数组的 includes 方法
假设我们要根据所提供的食品类型返回相应的配料数组。如果使用if..else
语句,通常会这样写:
function getIngredients(foodType) {
let ingredients = [];
if (foodType === 'pizza') {
ingredients = ['cheese', 'sauce', 'pepperoni'];
} else if (foodType === 'burger') {
ingredients = ['bun', 'beef patty', 'lettuce', 'tomato', 'onion'];
} else if (foodType === 'taco') {
ingredients = ['tortilla', 'beef', 'lettuce', 'shredded cheese'];
} else if (foodType === 'sandwich') {
ingredients = ['bread', 'turkey', 'lettuce', 'mayo'];
} else {
console.log('Unknown food type');
}
return ingredients;
}
这样的场景下,就可以使用includes
方法来优化这个冗长的if...else
语句:
function getIngredients(foodType) {
const menu = [
{food: 'pizza', ingredients: ['cheese', 'sauce', 'pepperoni']},
{food: 'burger', ingredients: ['bun', 'beef patty', 'lettuce', 'tomato', 'onion']},
{food: 'taco', ingredients: ['tortilla', 'beef', 'lettuce', 'shredded cheese']},
{food: 'sandwich', ingredients: ['bread', 'turkey', 'lettuce', 'mayo']}
];
const item = menu.find(menuItem => menuItem.food === foodType);
if (item === undefined) {
console.log('Unknown food type');
return [];
}
return item.ingredients;
}
这样做的好处在于:
- 可读性更强:使用数组和对象结合来代替
if..else
语句,代码更加简洁易懂,并且减少了嵌套,更容易阅读和理解。 - 扩展性更好:添加新的菜单项只需要向
menu
数组中添加一个新的对象即可,而不需要修改原函数getIngredients
,大大提高了代码的扩展性。 - 可维护性更好:当想要删除或更新某个菜单项时,只需要修改
menu
数组即可,而不需要修改原函数getIngredients
。因此,代码的可维护性也更好。
5、使用对象或者Map
5.1 对象
比如我们通常会遇到这种场景:
function getStatusColor (status) {
if (status === 'success') {
return 'green'
}
if (status === 'warning') {
return 'yellow'
}
if (status === 'info') {
return 'blue'
}
if (status === 'error') {
return 'red'
}
}
复制代码
const statusColors = {
success: 'green',
warning: 'yellow',
info: 'blue',
error: 'red'
};
function getStatusColor(status) {
return statusColors[status] || '';
}
这样做的好处有两个:
- 可读性更好:当需要增加新的状态时,只需要添加一个新的键值对即可。这比原来的
if..else
语句更易于阅读和维护。 - 执行效率更高:因为在JavaScript中,对象的属性访问是常数时间,而
if..else
语句则需要逐个匹配条件。由于我们只需要访问一个属性,所以使用对象映射比if..else
语句更有效率。
5.2 Map
来看下面的例子,我们要根据颜色打印水果:
function test(color) {
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
test(null); // []
test('yellow'); // ['banana', 'pineapple']
复制代码
我们可以使用Map
数据结构来优化上面的代码,具体实现如下:
const foodMap = new Map([
['red', ['apple', 'strawberry']],
['yellow', ['banana', 'pineapple']],
['purple', ['grape', 'plum']]
]);
function test(color) {
return foodMap.get(color) || [];
}
这样做的好处在于:
- 这段代码更加简洁:使用
Map
数据结构可以使代码更短,可读性也更强。 - 可维护性更高:将数据存储在
Map
中,如果需要修改或添加一个颜色和对应的食物列表,只需在Map
中修改或添加一条记录即可。 - 搜索时间更短:由于
Map
内部使用了哈希表来存储键值对,所以搜索时间可能比基于条件语句的方法更快。当有大量数据时,这种优化可能会显著提高代码的性能。
6、减少嵌套,提前返回
6.1 场景1
假设我们需要根据用户的年龄,返回一个字符串表示他们的人生阶段。使用if..else
语句,一般可能会这样写:
function getLifeStage(age) {
let stage;
if (age < 1) {
stage = 'baby';
} else {
if (age < 4) {
stage = 'toddler';
} else {
if (age < 13) {
stage = 'child';
} else {
if (age < 20) {
stage = 'teenager';
} else {
if (age < 65) {
stage = 'adult';
} else {
stage = 'senior';
}
}
}
}
}
return `Your life stage is ${stage}`;
}
我们可以使用减少嵌套和提前返回的方法来优化这个冗长的if...else
语句,代码如下所示:
function getLifeStage(age) {
if (age < 1) {
return 'Your life stage is baby';
}
if (age < 4) {
return 'Your life stage is toddler';
}
if (age < 13) {
return 'Your life stage is child';
}
if (age < 20) {
return 'Your life stage is teenager';
}
if (age < 65) {
return 'Your life stage is adult';
}
return 'Your life stage is senior';
}
这样做的好处在于:
- 可读性更强:减少了嵌套层次以及大括号,使代码更加简洁明了,并且易于阅读和理解。
- 扩展性更好:当需要添加或删除一个年龄段时,只需要在相应的位置插入或删除一个if语句即可,而不需要影响其他部分的代码,大大提高了代码的扩展性。
- 错误处理更好:每个if语句可以处理一种情况,这样可以更及时地返回正确的结果。同时,避免了多个判断条件匹配的情况下重复执行同一个代码块的情况。
6.2 场景2
来看看下面这个例子:
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 条件1: fruit 必须非空
if (fruit) {
// 条件2: 必须是红色的水果
if (redFruits.includes(fruit)) {
console.log('red');
// 条件3: 必须大于10个
if (quantity > 10) {
console.log('big quantity');
}
}
} else {
throw new Error('No fruit!');
}
}
test(null); // error: No fruits
test('apple'); // red
test('apple', 20); // red, big quantity
看看上面的代码,我们有:
- 1个if/else语句,过滤掉无效的条件
- 3层嵌套的if语句(条件1、2和3)。
我个人遵循的一般规则是在将一些比较容易处理、计算量小、小概率的提前返回。
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 先判断小概率情况,提前返回
if (!fruit) throw new Error('No fruit!');
if (redFruits.includes(fruit)) {
console.log('red');
if (quantity > 10) {
console.log('big quantity');
}
}
}
通过这样做,我们就少了一层嵌套语句。这种编码方式很好,特别是当你有很长的if语句时(想象一下,你需要滚动到最下面才知道有一个else语句,这可读性可能不会太好)。我们可以进一步减少嵌套的if,通过倒置条件和提前返回。比如:
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (!fruit) throw new Error('No fruit!');
if (!redFruits.includes(fruit)) return;
console.log('red');
if (quantity > 10) {
console.log('big quantity');
}
}
通过颠倒条件2的条件,我们的代码现在已经没有了嵌套的语句,代码变得很简洁直观。
7、隐藏高阶技能:find(Boolean)
写 react 的同学可能在一些组件复用的场景会经常遇到这种情况:组件中有少部分组件会根据传入的props属性不同进行展示。
function ComponentByType({ type }) {
if (type === "type_1") {
return (
<>
{/* 其他很复杂的组件 */}
<Component1 />
{/* 其他很复杂的组件 */}
</>
);
}
if (type === "type_2") {
return (
<>
{/* 其他很复杂的组件 */}
<Component2 />
{/* 其他很复杂的组件 */}
</>
);
}
if (type === "type_3") {
return (
<>
{/* 其他很复杂的组件 */}
<Component3 />
{/* 其他很复杂的组件 */}
</>
);
}
if (type === "type_4") {
return (
<>
{/* 其他很复杂的组件 */}
<Component4 />
{/* 其他很复杂的组件 */}
</>
);
}
}
而这种情况,可以通过find(Boolean)
这个小技巧来获得极大的简化:
function ComponentByType({ type }) {
return (
<>
{/* 其他很复杂的组件 */}
<>
{
[
type === "type_1" && <Component1 />,
type === "type_2" && <Component2 />,
type === "type_3" && <Component3 />,
type === "type_4" && <Component4 />
].find(Boolean)
}
</>
{/* 其他很复杂的组件 */}
</>
);
}
find(Boolean)
可以实现条件判断,因为在JavaScript中,布尔值的true和false可以被转换为数字1和0。在这个例子里,当某个条件不满足时,对应的React组件(例如<Component1 />
)会被解析为布尔值false并存储在数组中,相反当条件满足时,对应的React组件会被解析为一个真值true。这样,使用find(Boolean)
就可以找到第一个真值(即第一个满足条件的React组件),并把它渲染到页面上。
需要注意的是,当没有任何一个条件满足时,
.find()
方法返回的是undefined,会导致React渲染错误。但由于在这个例子中我们把结果用短路语法忽略了,因此不会有报错。
如果希望更准确地处理没有满足条件的情况,可以显式地在.find()
后面添加一个默认返回值,在找不到满足条件的组件时返回默认组件。例如:
[
type === "type_1" && <Component1 />,
type === "type_2" && <Component2 />,
type === "type_3" && <Component3 />,
type === "type_4" && <Component4 />
].find(Boolean) || <DefaultComponent />
这样,当四种类型都不匹配时,就会默认渲染 <DefaultComponent>
组件,避免了React渲染错误。通过使用find(Boolean)
,可以简化代码,避免了使用多个if/else
语句或switch/case
语句的复杂性和冗长性。
原理是什么呢?
find(Boolean)
的作用是查找数组中第一个布尔值为true
的元素,并返回该元素的值。数组中包含多个条件语句以及相应的React组件,当某个条件满足时,对应的组件会被返回并渲染到页面上;否则会返回false
,由于我们使用了条件运算符&&
,所以这个返回值会被自动忽略。
比如一个简单的例子:
[0,false,null,1,2].find(Boolean) // 返回 1
在不加 new
的情况下调用 Boolean
函数会将传入的参数转换为布尔值。对于数组中的各个元素,利用 Boolean
函数进行类型转换后的结果如下:
0
被转换为false
false
本身就是布尔值false
null
被转换为false
1
被转换为true
2
被转换为true
因此,使用 find
方法查找第一个真值时,返回了第一个布尔值为 true
的元素的索引。在这个数组中,第一个布尔值为 true
的元素是 1
,它的索引是 3,因此 find
方法返回了 1。需要注意的是,如果该数组中不存在任何布尔值为 true
的元素,则 find
方法返回 undefined。
[0,false,null,1,2].map(Boolean); // [false, false, false, true, true]
[0,false,null,1,2].find(Boolean); // 1
其中,map
方法将数组中的每个元素都应用一次 Boolean
函数,得到一个新的布尔类型的数组。可以看到,在这个新数组中,第一个布尔值为 true
的元素是 true
,它的索引是 3。因此,find
方法在原始数组中查找第一个布尔值为 true
的元素时,返回了 1。
8、责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以使多个对象都有机会处理请求。在 JavaScript 中,我们可以利用责任链模式来优化复杂、冗长的 if...else
语句。
在责任链模式中,各个处理者构成了一个链条,当有请求发生时,这些处理者按照顺序依次尝试处理请求,直到有处理者成功处理请求或所有处理者尝试完毕为止。因此,责任链模式可以动态地组织和修改处理流程,从而提高代码的灵活性。
在实际工作中,有很多场景可以使用责任链模式来优化复杂、冗长的 if...else
语句。
比如,在一个电商平台上,用户下单后需要进行一系列的校验和处理,比如校验商品库存是否充足、校验收货地址是否正确等。而这些校验和处理操作可能会有多个步骤,如果采用传统的 if...else
语句来实现,代码很容易变得冗长难懂。下面是一个简单的示例代码:
function placeOrder(order) {
if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
console.log("At least one item should be specified to place an order.");
return;
}
// 校验商品库存是否充足
for (let i = 0; i < order.items.length; i++) {
let item = order.items[i];
if (!checkStock(item.productId, item.quantity)) {
console.log(`Product ${item.productId} is out of stock.`);
return;
}
}
if (!order.address || !isValidAddress(order.address)) {
console.log("Invalid address.");
return;
}
// 扣减库存
for (let i = 0; i < order.items.length; i++) {
let item = order.items[i];
decreaseStock(item.productId, item.quantity);
}
// 生成订单
let orderId = generateOrderId();
console.log(`Order ${orderId} placed successfully.`);
}
在上述代码中,我们使用了多个 if...else
语句来完成订单校验和处理操作。这样的代码难以维护和扩展,而且不符合开闭原则(对扩展开放,对修改关闭)。
使用责任链模式可以有效解决这个问题。下面是一个使用责任链模式来实现订单处理的示例代码:
class OrderValidator {
constructor() {
this.nextHandler = null;
}
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(order) {}
}
class ItemValidator extends OrderValidator {
handle(order) {
if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
console.log("At least one item should be specified to place an order.");
return false;
} else {
return this.nextHandler.handle(order);
}
}
}
class StockValidator extends OrderValidator {
handle(order) {
for (let i = 0; i < order.items.length; i++) {
let item = order.items[i];
if (!checkStock(item.productId, item.quantity)) {
console.log(`Product ${item.productId} is out of stock.`);
return false;
}
}
return this.nextHandler.handle(order);
}
}
class AddressValidator extends OrderValidator {
handle(order) {
if (!order.address || !isValidAddress(order.address)) {
console.log("Invalid address.");
return false;
} else {
return this.nextHandler.handle(order);
}
}
}
class StockDeductor extends OrderValidator {
handle(order) {
for (let i = 0; i < order.items.length; i++) {
let item = order.items[i];
decreaseStock(item.productId, item.quantity);
}
return this.nextHandler.handle(order);
}
}
class OrderGenerator extends OrderValidator {
handle(order) {
let orderId = generateOrderId();
console.log(`Order ${orderId} placed successfully.`);
return true;
}
}
let itemValidator = new ItemValidator();
let stockValidator = new StockValidator();
let addressValidator = new AddressValidator();
let stockDeductor = new StockDeductor();
let orderGenerator = new OrderGenerator();
itemValidator.setNext(stockValidator).setNext(addressValidator).setNext(stockDeductor).setNext(orderGenerator);
function placeOrder(order) {
itemValidator.handle(order);
}
在上述代码中,我们定义了多个订单处理器(比如 ItemValidator
、StockValidator
等),它们继承自 OrderValidator
类。这些处理器构成了一个处理链,并通过 setNext()
方法连接起来。当用户下单后,我们只需要调用 placeOrder()
函数并将订单对象作为参数传入即可。
在处理过程中,每个处理器都会检查订单是否满足其要求。如果满足就继续交给下一个处理器去处理,否则直接返回。如果所有处理器都能够成功处理订单,最终会输出 “Order x placed successfully.” 的提示信息。
使用责任链模式的好处:
- 代码更加灵活和易于扩展:在需要添加或修改处理步骤时,只需要新增或修改相应的处理器类即可,不需要修改原有的代码。
- 代码复用性更高:不同的处理器类可以被复用,具有更好的可读性和可维护性。
- 代码结构更加清晰和易于理解:使用责任链模式将系统分解成多个小而简单的部分,每一个部分都有自己的处理逻辑和职责。这样的代码结构更加清晰和易于理解。
作者:你当像鸟飞往你的山