在編程中,解決同一個(gè)問題通常有多種方法。這些解決方案在不同方面可能有所不同,例如長度、性能、使用的算法、可讀性等。

在本文中,我們將研究幾種快速簡潔的單行解決方案,以解決 JavaScript 中經(jīng)常出現(xiàn)的各種問題。
什么是單行代碼?
在我們開始之前,讓我們確保我們了解是什么單行代碼。
單行代碼是問題的代碼解決方案,使用特定編程語言中的單個(gè)語句實(shí)現(xiàn),無需任何第三方實(shí)用程序。
該定義包含許多其他定義中沒有的重要區(qū)別特征:
1). “……單句……”
并非每一段只占用一行的代碼都是單行代碼。例如,看看這個(gè)將兩個(gè)平方和相加并返回結(jié)果的方法。
const sum = (a, b) => { const s1 = a * a; const s2 = b * b; return s1 + s2; }
你會(huì)稱之為單行代碼嗎?在大多數(shù)情況下,這只會(huì)作為格式錯(cuò)誤的代碼通過。Prettier 之類的工具可以輕松地將這三個(gè)語句自動(dòng)拆分為多行。
獲得兩個(gè)平方和的真正單行方法是這樣的:
一個(gè)簡短、簡潔的陳述可以同樣清晰地完成同樣的工作。
另一方面,此方法跨越多行代碼以提高可讀性,但它仍然可以作為一行代碼通過:
const capitalizeWithoutSpaces = (str) =>
str
.split('')
.filter((char) => char.trim())
.map((char) => char.toUpperCase())
.join('');
因此,盡管名稱如此,“單行”并不一定意味著是一行代碼。
2). “……特定的編程語言……”
這與每個(gè)高級(jí)語言程序在執(zhí)行前都必須翻譯成低級(jí)語言這一事實(shí)有關(guān)。一個(gè)非常簡單的程序最終可能會(huì)占用數(shù)十或數(shù)百行匯編代碼和機(jī)器代碼。
例如,這里是另一個(gè)也添加兩個(gè)平方和的單行代碼,這次是在 C++ 中:
int sum(int a, int b) {
return a * a + b * b;
}
讓我們看看編譯成匯編語言后的樣子:
sum(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, eax
mov edx, eax
mov eax, DWORD PTR [rbp-8]
imul eax, eax
add eax, edx
pop rbp
ret
這個(gè)匯編程序顯然不止一行或一行代碼。 想象一下等效的機(jī)器語言程序會(huì)有多少。 所以這個(gè)函數(shù)可以說是僅在 C++ 上下文中的單行函數(shù)。
3). “……沒有任何第三方實(shí)用程序”
對(duì)于單行代碼,它不應(yīng)該引用編程語言本身不可用的任何方法或函數(shù),記住我們之前看過的單行代碼:
const capitalizeWithoutSpaces = (str) =>
str
.split('')
.filter((char) => char.trim())
.map((char) => char.toUpperCase())
.join('');
這里使用的所有方法都是內(nèi)置的 JavaScript 方法。 它不包括來自 NPM 或其他地方的第三方代碼。
但是,如果我們決定實(shí)現(xiàn)自己的 filter() 方法來替換 Array filter(),則該方法將不再符合單行方法的條件。
// Not a one-liner
const capitalizeWithoutSpaces = (str) =>
filter(str.split(''), (char) => char.trim())
.map((char) => char.toUpperCase())
.join('');
function filter(arr, callback) {
// Look at all these lines
const result = [];
for (const item of arr) {
if (callback(item)) {
result.push(item);
}
}
return result;
}
拋開定義,現(xiàn)在讓我們看一些聰明的 JavaScript 單行代碼以及它們解決方案。
1. 獲取數(shù)組的最小元素
要獲得數(shù)組中的最小項(xiàng),我們可以采用這種使用 for 循環(huán)和 if 語句的命令式方法。
const getSmallest = (arr) => {
let smallest = Number.POSITIVE_INFINITY;
for (const num of arr) {
if (num < smallest) {
smallest = num;
}
}
return smallest;
};
const arr = [13, 7, 11, 3, 9, 15, 17];
console.log(getSmallest(arr)); // 3
這沒關(guān)系,但有一個(gè)簡潔且聲明性的單行替代方案同樣有效:
const getSmallest = (arr) =>
arr.reduce((smallest, num) => Math.min(smallest, num));
const arr = [13, 7, 11, 3, 9, 15, 17];
console.log(getSmallest(arr)); // 3
2. 獲取數(shù)組的最大元素
這是獲取數(shù)組中最大元素的可接受方法。
const getLargest = (arr) => {
let largest = Number.NEGATIVE_INFINITY;
for (const num of arr) {
if (num > largest) {
largest = num;
}
}
return largest;
};
const arr = [13, 7, 11, 3, 9, 15, 17];
console.log(getLargest(arr)); // 17
但就像我們看到的獲取最小數(shù)組元素一樣,有一種更短、更簡潔的方法。
const getLargest = (arr) =>
arr.reduce((largest, num) => Math.max(largest, num));
const arr = [13, 7, 11, 3, 9, 15, 17];
console.log(getLargest(arr)); // 17
您可以看到,此函數(shù)與單行 getSmallest() 函數(shù)之間的唯一區(qū)別是 Math.min() 已替換為 Math.max()。
3. 打亂數(shù)組
數(shù)組/列表洗牌的一個(gè)常見用途是在紙牌游戲中,其中牌組中的牌必須隨機(jī)排序。
Fisher-Yates 洗牌是一種著名的洗牌算法。 查看它在 JavaScript 中的可能實(shí)現(xiàn):
const shuffleArray = (arr) => {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
};
const arr = [1, 2, 3, 4, 5];
shuffleArray(arr);
// [ 2, 3, 5, 1, 4 ] (varies)
console.log(arr);
用一些函數(shù)式編程魔法重構(gòu)它,我們有:
const shuffleArray = (arr) =>
[...Array(arr.length)]
.map((_, i) => Math.floor(Math.random() * (i + 1)))
.reduce(
(shuffled, r, i) =>
shuffled.map((num, j) =>
j === i ? shuffled[r] : j === r ? shuffled[i] : num
),
arr
);
// [ 2, 4, 1, 3, 5 ] (varies)
console.log(shuffleArray([1, 2, 3, 4, 5]));
這以 O(n2) 時(shí)間復(fù)雜度(二次)運(yùn)行,并且可能會(huì)導(dǎo)致大型數(shù)組出現(xiàn)性能問題,但它是一種優(yōu)雅的解決方案。 此外,與第一種方法不同,它不會(huì)改變?cè)紨?shù)組。
另一種函數(shù)式方法利用 Array sort() 方法的實(shí)現(xiàn)方式來隨機(jī)排列數(shù)組。
const shuffleArray = (arr) => arr.sort(() => Math.random() - 0.5);
const arr = [1, 2, 3, 4, 5];
// [ 5, 2, 4, 1, 3 ] (varies)
console.log(shuffleArray(arr));
由于它使用了 sort(),因此,它的運(yùn)行時(shí)間復(fù)雜度為 O(n log n),并且比前面的方法具有更好的性能。
4. 按對(duì)象屬性對(duì)數(shù)組進(jìn)行分組
有時(shí)我們需要使用它們都具有的特定屬性對(duì)一組對(duì)象進(jìn)行分組,例如,按國家/地區(qū)對(duì)用戶進(jìn)行分組,按出版年份對(duì)書籍進(jìn)行分組,按顏色對(duì)汽車進(jìn)行分組等。
在下面的示例中,我們根據(jù)姓名的長度將人物對(duì)象分組到一個(gè)數(shù)組中。
const groupBy = (arr, groupFn) => {
const grouped = {};
for (const obj of arr) {
const groupName = groupFn(obj);
if (!grouped[groupName]) {
grouped[groupName] = [];
}
grouped[groupName].push(obj);
}
return grouped;
};
const people = [
{ name: 'Matt' },
{ name: 'Sam' },
{ name: 'John' },
{ name: 'Mac' },
];
const groupedByNameLength = groupBy(people, (person) => person.name.length);
/**
{
'3': [ { name: 'Sam' }, { name: 'Mac' } ],
'4': [ { name: 'Matt' }, { name: 'John' } ]
}
*/
console.log(groupedByNameLength);
這是單行代碼的解決方案:
const groupBy = (arr, groupFn) =>
arr.reduce(
(grouped, obj) => ({
...grouped,
[groupFn(obj)]: [...(grouped[groupFn(obj)] || []), obj],
}),
{}
);
const people = [
{ name: 'Matt' },
{ name: 'Sam' },
{ name: 'John' },
{ name: 'Mac' },
];
const groupedByNameLength = groupBy(people, (person) => person.name.length);
/**
{
'3': [ { name: 'Sam' }, { name: 'Mac' } ],
'4': [ { name: 'Matt' }, { name: 'John' } ]
}
*/
console.log(groupedByNameLength);
5.反轉(zhuǎn)字符串
我們可以在 JavaScript 中使用反向 for 循環(huán)來反轉(zhuǎn)字符串,如下所示:
const reverseString = (str) => {
let reversed = '';
for (let i = str.length - 1; i >= 0; i--) {
const ch = str[i];
reversed += ch;
}
return reversed;
};
const reverse = reverseString('javascript');
console.log(reverse); // tpircsavaj
但是再一次,我們可以利用強(qiáng)大的內(nèi)置數(shù)組方法,如 reverse() 和 join() 來創(chuàng)建一個(gè)做同樣事情的單行代碼。
const reverseString = (str) => str.split('').reverse().join('');
const reverse = reverseString('javascript');
console.log(reverse); // tpircsavaj
6. 生成隨機(jī)的十六進(jìn)制顏色
十六進(jìn)制顏色代碼是指定 RGB 顏色的一種方式。 它們具有#RRGGBB 格式,其中 RR 代表紅色,GG 代表綠色,BB 代表藍(lán)色。 每種顏色的值范圍從 0 到 255,并以十六進(jìn)制格式表示 - 0 到 FF。
這個(gè)單行生成一個(gè)隨機(jī)的十六進(jìn)制顏色并返回結(jié)果。
const randomHexColor = () =>
`#${Math.random().toString(16).slice(2, 8).padEnd(6, '0')}`;
console.log(randomHexColor()); // #7a10ba (varies)
console.log(randomHexColor()); // #65abdc (varies)
7. 獲取數(shù)組的平均值
這是眾多問題中的另一個(gè)問題,其中涉及循環(huán)的解決方案可以使用一種或多種 Array 方法來縮短。
因此,雖然我們可以像這樣獲得數(shù)組中數(shù)字的平均值:
const getAverage = (arr) => {
let sum = 0;
for (const num of arr) {
sum += num;
}
return sum / arr.length;
};
const arr = [5, 13, 9, 11, 10, 15, 7];
const average = getAverage(arr);
console.log(average); // 10
Array reduce() 方法讓我們創(chuàng)建了這個(gè)緊湊的單行替代方案:
const getAverage = (arr) => arr.reduce((sum, num) => sum + num, 0) / arr.length;
const arr = [5, 13, 9, 11, 10, 15, 7];
const average = getAverage(arr);
console.log(average); // 10
8. 檢查兩個(gè)數(shù)組是否包含相同的值
這是一個(gè)確保兩個(gè)數(shù)組包含相同元素(以任何順序)并且這些元素在兩個(gè)數(shù)組中出現(xiàn)相同次數(shù)的問題。
使用 for 循環(huán),我們可以實(shí)現(xiàn)以下解決方案:
const areEqual = (arr1, arr2) => {
if (arr1.length === arr2.length) {
for (const num of arr1) {
if (!arr2.includes(num)) {
return false;
}
}
return true;
}
return false;
};
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 1, 2];
const arr3 = [1, 2, 3];
console.log(areEqual(arr1, arr2)); // true
console.log(areEqual(arr1, arr3)); // false
使用 Array sort() 和 join() 方法,我們可以創(chuàng)建這個(gè)單行替代方案:
const areEqual = (arr1, arr2) =>
arr1.sort().join(',') === arr2.sort().join(',');
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 1, 2];
const arr3 = [1, 2, 3];
console.log(areEqual(arr1, arr2)); // true
console.log(areEqual(arr1, arr3)); // false
9. 從數(shù)組中刪除重復(fù)項(xiàng)
我們可以像這樣從數(shù)組中刪除重復(fù)項(xiàng):
const removeDuplicates = (arr) => {
const result = [];
for (const num of arr) {
if (!result.includes(num)) {
result.push(num);
}
}
return result;
};
const arr = [1, 2, 3, 4, 5, 3, 1, 2, 5];
const distinct = removeDuplicates(arr);
console.log(distinct); // [1, 2, 3, 4, 5]
但是我們可以利用 Set() 構(gòu)造函數(shù)在短短一行中刪除重復(fù)項(xiàng):
const removeDuplicates = (arr) => [...new Set(arr)];
const arr = [1, 2, 3, 4, 5, 3, 1, 2, 5];
const distinct = removeDuplicates(arr);
console.log(distinct); // [1, 2, 3, 4, 5]
10. 將Map轉(zhuǎn)換為 JSON
這個(gè)簡短的函數(shù)讓我們可以快速將 Map 對(duì)象轉(zhuǎn)換為 JSON 字符串而不會(huì)丟失任何信息:
const mapToJson = (map) => JSON.stringify(Object.fromEntries(map));
const map = new Map([
['user1', 'John'],
['user2', 'Kate'],
['user3', 'Peter'],
]);
const json = mapToJson(map);
// {"user1":"John","user2":"Kate","user3":"Peter"}
console.log(json);
另一個(gè)一行可以反轉(zhuǎn)上面的轉(zhuǎn)換。 以下函數(shù)會(huì)將 JSON 字符串轉(zhuǎn)換為 Map 對(duì)象。
const jsonToMap = (json) => new Map(Object.entries(JSON.parse(json)));
const json = '{"user1":"John","user2":"Kate","user3":"Peter"}';
const map = jsonToMap(json);
// Kate
console.log(map.get('user2'));
// Map(3) { 'user1' => 'John', 'user2' => 'Kate', 'user3' => 'Peter' }
console.log(map);
12. 將蛇形字符串轉(zhuǎn)換為駝峰大小寫
在蛇形字符串中,每個(gè)單詞由下劃線 (_) 分隔并以小寫字母開頭,例如:variable_name、bread_and_eggs 等。
但是,對(duì)于駝峰式字符串,第一個(gè)單詞以小寫字母開頭,后面的單詞均以大寫字母開頭。 單詞之間沒有空格或標(biāo)點(diǎn)符號(hào)。 駝峰式字符串的示例有:variableName、breadAndEggs 等。
使用這個(gè)簡潔的函數(shù),我們可以將任何蛇形大小寫的字符串轉(zhuǎn)換為駝峰大小寫。
const snakeToCamelCase = (s) =>
s.toLowerCase().replace(/(_\w)/g, (w) => w.toUpperCase().substr(1));
const str1 = 'learn_javascript';
const str2 = 'coding_beauty';
console.log(snakeToCamelCase(str1)); // learnJavaScript
console.log(snakeToCamelCase(str2)); // codingBeauty
13.生成隨機(jī)UUID
“UUID”是大學(xué)唯一標(biāo)識(shí)符的首字母縮寫詞。 UUID 是一個(gè) 128 位的值,可唯一標(biāo)識(shí) Internet 上的對(duì)象或?qū)嶓w。
這個(gè)單行生成一個(gè)隨機(jī) UUID:
const generateRandomUUID = (a) =>
a
? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16)
: ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(
/[018]/g,
generateRandomUUID
);
console.log(generateRandomUUID()); // f138f635-acbd-4f78-9be5-ca3198c4cf34
console.log(generateRandomUUID()); // 8935bb0d-6503-441f-bb25-7bc685b5b5bc
14.條件流控制
我們可以使用嵌套的三元運(yùn)算符將 if...else 或 switch 語句轉(zhuǎn)換為單行語句。 考慮一個(gè)返回特定范圍內(nèi)數(shù)字的英文單詞形式的函數(shù)。
使用 if...else 語句,這樣的函數(shù)可以這樣實(shí)現(xiàn):
const getNumWord = (num) => {
if (num === 1) {
return 'one';
} else if (num === 2) {
return 'two';
} else if (num === 3) {
return 'three';
} else if (num === 4) {
return 'four';
} else return 'unknown';
};
console.log(getNumWord(1)); // one
console.log(getNumWord(3)); // three
console.log(getNumWord(7)); // unknown
使用 switch...case 語句:
const getNumWord = (num) => {
switch (num) {
case 1:
return 'one';
case 2:
return 'two';
case 3:
return 'three';
case 4:
return 'four';
default:
return 'unknown';
}
};
console.log(getNumWord(1)); // one
console.log(getNumWord(3)); // three
console.log(getNumWord(7)); // unknown
現(xiàn)在使用嵌套的三元組來創(chuàng)建單行代碼:
const getNumWord = (num) =>
num === 1
? 'one'
: num === 2
? 'two'
: num === 3
? 'three'
: num === 4
? 'four'
: 'unknown';
console.log(getNumWord(1)); // one
console.log(getNumWord(3)); // three
console.log(getNumWord(7)); // unknown
結(jié)論
我們已經(jīng)了解了針對(duì)常見JavaScript編程問題的簡明解決方案。 我們看到許多實(shí)例,其中包含多個(gè)語句的命令式解決方案被轉(zhuǎn)換為使用各種內(nèi)置方法和語言結(jié)構(gòu)的聲明式單行代碼。
這些緊湊的解決方案有時(shí)性能和可讀性較低,但使用它們可以證明您的編程能力和對(duì)語言的掌握程度。使用任何一種方法,我們都是需要根據(jù)具體的情況來使用。