0%

javascript-avoid-comments-with-refactoring

javascript-avoid-comments-with-refactoring

下面这段代码是今天在Youtube上看到的一个视频中的代码, 代码的功能是从一个字符串数组中找出所有最长的字符串:比如给定字符串数组:[“aba”, “aa”, “ad”, “vcd”, “aba”],则要求返回[“aba”, “vcd”, “aba”]。

可以看到这段代码中有很多注释,我们的目的是优化这段代码,让代码可以self explanation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// gets all longest strings
function longestString(inputArray) {
// initialize the longest string to first value
let { length } = inputArray[0];
for (let i = 1; i < inputArray.length; i++) {
// checks if current string is longer than current longest
if (length < inputArray[i].length) {
length = inputArray[i].length;
}
}
// filters out any values not equal to the longest string
const strs = inputArray.filter((word) => word.length === length);

// return the longest strings
return strs;
}

命名优化

可以从以下几个方面入手:

  1. 函数名字不够清晰,longestString是单数形式,且没有动词,一般函数名字都应该加动词,可以改为getLongestStrings - 这样我们就知道,这个函数返回的是多个字符串。而且是从输入参数中get而来的。
  2. 函数的参数名字不够清晰,inputArray是一个数组,但是我们不知道这个数组的内容是什么,而且input这个单词有点多余,参数当然是input的,没必要再加上input这个单词。可以改为stringArray

此时,我们的代码变成了这样:

1
2
3
function getLongestStrings(stringArray) {

}
  1. 原来的代码中有一个length变量,这个变量的名字不够清晰,并没有说明是什么长度,也无法表明是最大长度,可以改为longestStringLength
  2. 原代码中使用的是经典的for循环,在Modern JavaScript中,我们更推荐函数式编程,所以可以改为for...of循环或者使用Array.prototype.forEach,这样代码更加简洁。

此时代码变成下面的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getLongestStrings(stringArray) {
let longestStringLength = 0;

stringArray.forEach((str) => {
if (longestStringLength < str.length) {
longestStringLength = str.length;
}
});

const longestStrings = stringArray.filter(
(word) => word.length === longestStringLength
);

return longestStrings;
}

性能优化

此时代码中有一个forEach,有一个filter,这两个函数都是遍历数组的,所以实际上我们遍历了两次数组,不够高效,将其改为一次遍历。此时代码变成下面的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getLongestStrings(stringArray) {
let longestStringLength = 0;
let longestStrings = [];

stringArray.forEach((str) => {
if (str.length > longestStringLength) {
longestStringLength = str.length;
longestStrings = [str];
} else if (str.length === longestStringLength) {
longestStrings.push(str);
}
});

return longestStrings;
}

使用reduce优化

多数数组的遍历方法都可以使用reduce来实现,所以我们可以将上面的代码改为使用reduce来实现。

1
2
3
4
5
6
7
8
9
10
function getLongestStrings(stringArray) {
return stringArray.reduce((acc, str) => {
if (str.length > acc.maxLength) {
return { maxLength: str.length, longestStrings: [str] };
} else if (str.length === acc.maxLength) {
acc.longestStrings.push(str);
}
return acc;
}, { maxLength: 0, longestStrings: [] }).longestStrings;
}

坦率的说,使用reduce这一版的可读性不如上一版,大家酌情使用。

reduce通常用来处理比较简单的逻辑,比如累加一个数组中所有的数字。在这种情况下,reduce通常只接受一个参数,就是待处理的数组。

1
2
const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 10

如果逻辑比较复杂,那么reduce就要使用多个参数,比如上面的例子,我们实际上传入了如下两个参数,只不过我们把这两个参数封装到了一个对象中。

  • longesStringLength - 用来记录当前最长的字符串的长度
  • longestStrings - 用来记录当前最长的字符串

处理的时候数组longestStrings是不断变化的,如果当前字符串的长度等于longestStringLength,那么就把当前字符串加入到longestStrings中。(此过程数组元素不断增多,原有的元素还在),如果当前字符串的长度大于longestStringLength,那么就把longestStrings清空,然后把当前字符串加入到longestStrings中。(此过程原有数组元素被清空,只有当前元素)。

此外,我们还注意到,第一个if分支有return,而第二个没有,有点奇怪,其实原本可以这样写。

1
2
3
4
5
6
7
if (str.length > acc.maxLength) {
acc.maxLength = str.length;
acc.longestStrings = [str];
} else if (str.length === acc.maxLength) {
acc.longestStrings.push(str);
}
return acc;

但是,当str.length > acc.maxLength时,直接返回一个新对象也可以,这个对象会作为reduce的下一次迭代的第一个参数。

其实再进一步,我们可以在if-else中也返回一个新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (str.length > acc.maxLength) {
return { maxLength: str.length, longestStrings: [str] };
}

if (str.length === acc.maxLength) {
return {
maxLength: acc.maxLength,
longestStrings: [...acc.longestStrings, str],
};
}

/// str.length < acc.maxLength
return acc;