目录
4.1.1 借助对象的 { key, value } 结构 进行优化
4.2.1 使用 Map 结构优化多元判断(将多个查询条件,拼接成字符串)
4.2.2 使用 Map 结构优化多元判断(将多个查询条件,拼接成对象)
4.2.3 使用 Map 结构优化多元判断(将多个处理逻辑,抽成公共方法)
4.2.4 使用 Map 结构优化多元判断(将多个查询条件,抽成正则表达式)
1. 单个 if 语句优化策略
需求:当条件为真时,打印出日志内容
优化前
let flag = true;
if (flag) {
log();
}
function log() {
console.log("如果 flag 值为真,打印这段文字");
}
优化后
let flag = true;
flag && log();
function log() {
console.log("如果 flag 值为真,打印这段文字");
}
2. 单个 if else 语句优化策略
2.1 提前 return
需求:执行登录操作,如果用户名和密码输入框为空,则提示用户”用户名和密码不能为空”;如果都不为空,则执行登录的操作
优化前
let user = "lyrelion";
let password = "solution";
if (user && password) {
// 执行登录操作
} else {
return "用户名和密码不能为空";
}
优化后
排非策略,先排除为 false 的情形,在执行其他操作(干掉 else 分支)
let user = "lyrelion";
let password = "solution";
if (!user || !password) {
return "用户名和密码不能为空";
}
// 执行登录操作
2.2 使用条件三目运算符
使用场景:在不影响可读性的情况下,处理
if else
分支下简短的返回值、单个简短赋值语句、调用单个相应函数时,建议使用三目运算符
2.2.1 if else 分支下简短返回值
优化前
function demo(flag) {
if (flag) {
return "true";
} else {
return "false";
}
}
优化后
function demo(falg) {
return flag ? "true" : "false";
}
2.2.2 if else 分支下简短赋值
优化前
function demo(flag) {
let val = "";
if (flag) {
val = "true";
} else {
val = "false";
}
}
优化后
function demo(flag) {
let val = flag ? "true" : "false";
}
2.2.3 if else 分支下调用单个函数
优化前
function demo(flag) {
if (flag) {
success();
} else {
fail();
}
}
优化后
function demo(flag) {
flag ? success() : fail();
}
3. 多个 if else 嵌套优化策略
需求:后端大哥说,给你返回的数据里面如果有
userInfo
字段,并且userInfo
下面有hobby
字段并且有值,就显示hobby
里面的内容,否则页面hobby
不显示
let result = {
status: 200,
msg: "success",
data: {
userInfo: {
name: "doudou",
hobby: ["吃饭", "睡觉", "打豆豆"],
},
},
};
优化前
if (result.data) {
if (result.data.userInfo) {
if (Array.isArray(result.data.userInfo.hobby)) {
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
} else {
return "hobby字段为空";
}
} else {
return "hobby字段不是一个数组";
}
} else {
return "userInfo字段不存在";
}
} else {
return "data字段不存在";
}
if else 一般不建议嵌套超过三层,如果一段代码存在过多的 if else 嵌套,代码的可读性就会急速下降,后期维护难度也大大提高,应该尽量避免过多的 if else 嵌套
3.1 当发现无效条件时,提前返回
if (!result.data) return "data字段不存在";
if (!result.data.userInfo) return "userInfo字段不存在";
if (!Array.isArray(result.data.userInfo.boddy)) return "hobby字段不是一个数组";
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
}
3.2 try catch 适合严谨又图省事的前端
try {
// 有可能出现错误的代码写在这里
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
}
} catch (error) {
// 出错后的处理写在这里
}
如果 try 中的代码没有出错,则程序正常运行 try 中的内容后,不会执行 catch 中的内容
如果 try 中的代码出错,程序立即跳入 catch 中继续执行,try 中出错后的程序就不再执行了
3.3 可选链 optional chaining
对一个空值进行属性读取时,程序会抛出异常;在多级嵌套的对象中取属性值的时候,更容易出现这个问题
为了保证程序的健壮性,就需要确保对象不为空时,再读取下一级的值
// 可选链优化
if (result?.data?.userInfo?.hobby?.length) {
// 进行业务逻辑操作
}
操作符 *?.* 会检查操作符左边的值是否是空值。如果是 null 或 undefined,这个表达式就终止然后返回 undefined。否则,这个表达式继续执行检查
4. 多个 else if 分支优化策略
4.1 仅需判断一个条件
需求:有多个按钮,点击按钮后,执行不同的业务逻辑
优化前
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
*/
const onButtonClick = (type) => {
if (type === "1") {
showLog("女装");
jumpTo("womenPage");
} else if (type === "2") {
showLog("男装");
jumpTo("menPage");
} else if (type === "3") {
showLog("童装");
jumpTo("childPage");
} else if (type === "4") {
showLog("美妆");
jumpTo("makeupPage");
} else if (type === "5") {
showLog("箱包");
jumpTo("bagPage");
} else {
showLog("推荐好物");
jumpTo("recommendPage");
}
};
switch 优化(没从根本解决问题,看着还是很多):
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
*/
const onButtonClick = (type) => {
switch (type) {
case "1":
showLog("女装");
jumpTo("womenPage");
break;
case "2":
showLog("男装");
jumpTo("menPage");
break;
case "3":
showLog("童装");
jumpTo("childPage");
break;
case "4":
showLog("美妆");
jumpTo("makeupPage");
break;
case "5":
showLog("箱包");
jumpTo("bagPage");
break;
default:
showLog("推荐好物");
jumpTo("recommendPage");
}
};
注意:
不要忘记在每个 case 语句后放一个 break,case 语句只是指明了想要执行代码的起点,并没有指明终点;
如果没有在 case 语句中添加 break 语句,没有明确的中断流程,在每次条件判断后,都会执行下次判断条件,可能会造成逻辑混乱
4.1.1 借助对象的 { key, value } 结构 进行优化
借助对象结构,把判断条件作为对象的属性名,把处理逻辑要传入的参数作为对象的属性值;
在执行按钮点击事件时,通过查询对象中的键,获取到键对应的值,然后执行对应的处理逻辑
const actions = {
1: ["女装", "womenPage"],
2: ["男装", "menPage"],
3: ["童装", "childPage"],
4: ["美妆", "makeupPage"],
5: ["箱包", "bagPage"],
default: ["推荐好物", "recommendPage"],
};
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
*/
function onButtonClick(type) {
let action = actions[type] || actions["default"];
// 打印日志
showLog(action[0]);
// 跳转页面
jumpTo(action[1]);
}
4.1.2 借助 ES6 Map 数据结构 进行优化
Map 对象保存键值对,任何类型值(对象或者原始值) 都可以作为一个键或一个值
const actions = new Map([
["1", ["女装", "womenPage"]],
["2", ["男装", "menPage"]],
["3", ["童装", "childPage"]],
["4", ["美妆", "makeupPage"]],
["5", ["箱包", "bagPage"]],
["default", ["推荐好物", "recommendPage"]],
]);
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
*/
function onButtonClick(type) {
let action = actions.get(type) || actions.get("default");
showLog(action[0]);
jumpTo(action[1]);
}
ES6 Map 对象 VS Object 对象:
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算
4.2 需判断多个条件
需求:在点击按钮时不仅要判断 type,还要判断用户的身份 —— 男用户 or 女用户
优化前
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
* @param { string } sex // 男用户or女用户
*/
function onButtonClick(type, sex) {
if (sex === "women") {
if (type === "1") {
// do something
} else if (type === "2") {
// do something
} else if (type === "3") {
// do something
} else if (type === "4") {
// do something
} else if (type === "5") {
// do something
} else {
// do something
}
} else if (sex === "men") {
if (type === "1") {
// do something
} else if (type === "2") {
// do something
} else if (type === "3") {
// do something
} else if (type === "4") {
// do something
} else if (type === "5") {
// do something
} else {
// do something
}
}
}
从上方示例代码中可以看出,如果判断条件变为二元条件判断时,if else 的数量就增加到一元判断条件的二倍,代码看着更臃肿了
那么对于二元的条件判断,我们应该怎么去优化它们呢?
4.2.1 使用 Map 结构优化多元判断(将多个查询条件,拼接成字符串)
const actions = new Map([
[
"women_1",
() => {
/* do something */
},
],
[
"women_2",
() => {
/* do something */
},
],
[
"women_3",
() => {
/* do something */
},
],
[
"women_4",
() => {
/* do something */
},
],
[
"women_5",
() => {
/* do something */
},
],
[
"men_1",
() => {
/* do something */
},
],
[
"men_2",
() => {
/* do something */
},
],
[
"men_3",
() => {
/* do something */
},
],
[
"men_4",
() => {
/* do something */
},
],
[
"men_5",
() => {
/* do something */
},
],
[
"default",
() => {
/* do something */
},
],
]);
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
* @param { string } sex // 男用户 or 女用户
*/
function onButtonClick(type, sex) {
let action = actions.get(`$(sex)_$(type)`) || actions.get("default");
action.call(this);
}
上面这种处理思想是,把条件判断拼接成字符串作为键,每个分支下的处理逻辑作为值,在使用时传入参数使用 Map 查询,这种方法非常适合于二元或多元条件判断
4.2.2 使用 Map 结构优化多元判断(将多个查询条件,拼接成对象)
如果你不喜欢把查询条件拼接为字符串使用,这还有一种方法,把查询条件作为对象,借助 Map 数据结构实现
const actions = new Map([
[
{ sex: "women", type: "1" },
() => {
/* do something */
},
],
[
{ sex: "women", type: "2" },
() => {
/* do something */
},
],
[
{ sex: "women", type: "3" },
() => {
/* do something */
},
],
[
{ sex: "women", type: "4" },
() => {
/* do something */
},
],
[
{ sex: "women", type: "5" },
() => {
/* do something */
},
],
[
{ sex: "men", type: "1" },
() => {
/* do something */
},
],
[
{ sex: "men", type: "2" },
() => {
/* do something */
},
],
[
{ sex: "men", type: "3" },
() => {
/* do something */
},
],
[
{ sex: "men", type: "4" },
() => {
/* do something */
},
],
[
{ sex: "men", type: "5" },
() => {
/* do something */
},
],
[
"default",
() => {
/* do something */
},
],
]);
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
* @param { string } sex // 男用户or女用户
*/
function onButtonClick(type, sex) {
// 根据条件使用filter查询
let action = [...actions].filter(
([key, value]) => key.sex === sex && key.type === type
);
action.forEach(([key, value]) => value.call(this));
}
/**
* 按钮点击事件
* @param { string } type // 1女装2男装3童装4美妆5箱包
* @param { string } sex // 男用户or女用户
*/
function onButtonClick(type, sex) {
// 根据条件使用find查询
let action = [...actions].find(
([key, value]) => key.sex === sex && key.type === type
);
action[1].call(this);
}
上方根据条件查询相应执行逻辑时,提供了 filter 和 find 两种查询方式
个人觉得使用 filter 更为正式,使用 find 更容易阅读
从这里我们就看出了使用 Map 相对于 Object 存在的优势了 → Map 数据结构可以以任意类型的值作为 key
4.2.3 使用 Map 结构优化多元判断(将多个处理逻辑,抽成公共方法)
假如在 women 情况下,type 为 1,2,3,4 时的处理逻辑都一样时,该怎么写呢?
const logicA = () => {
/* 执行A逻辑 */
};
const logicB = () => {
/* 执行B逻辑 */
};
const actions = new Map([
[{ sex: "women", type: "1" }, logicA],
[{ sex: "women", type: "2" }, logicA],
[{ sex: "women", type: "3" }, logicA],
[{ sex: "women", type: "4" }, logicA],
[{ sex: "women", type: "5" }, logicB],
// ……
]);
4.2.4 使用 Map 结构优化多元判断(将多个查询条件,抽成正则表达式)
上面的写法虽然 Map 中结构清晰了,日常需求的话可以这么写也是没什么问题的
但是,如果以后增加需求,women 条件下 type 为 6、7、8、10、11……的逻辑处理都是一样的,难道还要用上方那样的写法吗?
function actions() {
const logicA = () => {
/* 执行A逻辑 */
};
const logicB = () => {
/* 执行B逻辑 */
};
const action = new Map([
[/^women_[1-4]$/, logicA],
[/^women_5$/, logicB],
// ……
]);
}
利用正则进行判断条件匹配后,代码又清爽了许多。并且这里使用 Map 后的优势就更加的明显了,符合正则的条件的公共逻辑都会执行
4.2.5 总结不同的条件判断时,采用的优化方式
一元条件判断:存到 Object 中。
一元条件判断:存到 Map 中。
二元或多元判断:将判断条件拼接成字符串存到 Object 中
二元或多元判断:将判断条件拼接成字符串存到 Map 中
多元判断时:将判断条件写成 Object 存到 Map 中
多元判断时:将判断条件写成正则存到 Map 中
5. 使用数组新特性优化逻辑判断
在工作中,巧妙的使用 ES6 中提供的数组新特性,也可以达到轻松优化逻辑判断的效果
5.1 使用 includes 优化代码逻辑
需求:判断 animal 是否属于 cute 类型
优化前
const cuteAnimal = ["dog", "cat", "bird", "panda"];
function animalJudge(animal) {
if (
animal === "dog" ||
animal === "cat" ||
animal === "bird" ||
animal === "panda"
) {
console.log("可爱的小动物");
}
}
优化后
const cuteAnimal = ["dog", "cat", "bird", "panda"];
function animalJudge(animal) {
if (cuteAnimal.includes(animal)) {
console.log("可爱的小动物");
}
}
5.2 使用 every 优化代码逻辑
需求:判断 animals 数组中的动物是否都属于 cute 类型
every()
:判断数组的每一项是否都满足条件,都满足条件返回 true
,否则返回 false
优化前
const animals = [
{
name: "dog",
type: "cute",
},
{
name: "cat",
type: "cute",
},
{
name: "elephant",
type: "tall",
},
];
function type() {
let isAllCute = true;
// 判断条件:animals中的动物是否都是cute类型
for (let animal of animals) {
if (!isAllCute) break;
isAllRed = animal.type === "cute";
}
console.log(isAllCute); // false
}
优化后
const animals = [
{
name: "dog",
type: "cute",
},
{
name: "cat",
type: "cute",
},
{
name: "elephant",
type: "tall",
},
];
function animals() {
// 判断条件:animals中的动物是否都是cute类型
const isAllCute = animals.every((animal) => animal.type === "cute");
console.log(isAllCute); // false
}
5.3 使用 some 优化代码逻辑
需求:判断 animals 中的动物是否存在有 tall 类型的
some():对数组中每一项运行特定函数,如果有一项符合条件,则返回 true,都不符合条件返回 false
const animals = [
{
name: "dog",
type: "cute",
},
{
name: "cat",
type: "cute",
},
{
name: "elephant",
type: "tall",
},
];
function animals() {
// 判断条件:animals中的动物是否含有tall类型
const isHasTall = animals.some((animal) => animal.type === "tall");
console.log(isHasTall); // true
}
6. 默认值优化
优化前
function request(options) {
let method = options.method ? options.method : "GET";
let data = options.data ? options.data : {};
//...
}
优化后
function request(options) {
let method = options.method || "GET";
let data = options.data || {};
//...
}
基于 ES6 优化后
// 解析解构和默认参数搭配使用
function request({ method, data } = { method: "GET", data: {} }) {
//...
console.log(method); // GET
console.log(data); // {}
}
7. 使用策略模式优化分支逻辑
需求:咱马上要过国庆节啦,得打折清仓呀,有的商品 5 折,有的 7 折,有的 9 折~
优化前
function percent50(price) {
// 五折的算法
}
function percent70(price) {
// 七折的算法
}
function percent90(price) {
// 九折的算法
}
function calculatePrice(price) {
if (五折的商品) {
return percent50(price);
}
if (七折的商品) {
return percent50(price);
}
if (九折的商品) {
return percent50(price);
}
}
calculatePrice(price);
写到这里需求又来了,那以后的中秋节、元旦、情人节、元宵节……都要促销呀!再来个满 300 减 50,满 500 减 80,vip 用户满 500-150 上不封顶!对于这种越来越多的需求,还要深入函数内部增加if分支吗?
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式是一种对象行为型模式。
其实这个定义的基本意思就是说,根据策略选择要执行的函数,每一个策略都会有一个标识名,可以称为 key,而策略名对应的函数,可以称为 value(其实就是使用 key 寻找 value,然后执行 vlaue 的过程)
使用思路:定义一个对象封装不同的行为,提供选择接口,在不同条件下调用相应的行为
// 策略类
let strategy = {
// 5折
percent50(price) {
return price * 0.5;
},
// 7折
percent70(price) {
return price * 0.7;
},
// 9折
percent90(price) {
return price * 0.9;
},
// 满300-50
fullReduce50(price) {
// 执行满300-50的业务逻辑
},
// 满300-80
fullReduce80(price) {
// 执行满300-80的业务逻辑
},
// vip用户五折
vip50(price) {
// vip用户五折的业务逻辑
},
};
// 调用策略类中的方法
// 环境类
function calculatePrice(strategyName, price) {
return strategy[strategyName] && strategy[strategyName](price);
}
console.log(calculatePrice("percent50", 100)); // 50
上面例子中:
- 策略类 是指
strategy
对象,保存了所有的策略名对应的方法 - 环境类 是用接收策略名和其它参数,然后调用对应的策略方法
使用策略优化的好处:
- 有效的避免了多重条件选择语句。
- 策略模式提供了对 开放-封闭原则 的完美支持,将算法独立封装在 strategy 中,使得这些算法易于切换、易于理解、易于扩展
8. 总结与思考
更少的嵌套,尽早 return
倾向于使用对象或使用 map 结构来优化if else,而不是 Switch 语句
多重判断时使用 Array.includes
对 所有/部分 判断使用 Array.every & Array.some
使用默认参数和解构
当一个项目中需要大量算法,大量匹配模式时,可以考虑策略模式