8 種技巧讓你編寫更簡潔的 JavaScript 代碼
在本文中,我們將介紹 8 種不同的技術(shù)來幫助您編寫更簡潔的 JavaScript 代碼。
現(xiàn)在讓我們討論每種技術(shù),一次一個(gè)。
純函數(shù)
純函數(shù)是在給定相同輸入的情況下始終返回相同輸出的函數(shù)。除了提供的輸入之外,它不依賴于任何外部變量,也不影響/更改任何外部變量。擁有純函數(shù)使測試變得更加容易,因?yàn)樗鼈兪箿y試變得超級容易,因?yàn)槟梢噪S時(shí)存根/模擬輸入并測試您的預(yù)期值。讓我們看看下面的例子
let?name?=?"Yu?DongSu";
const?splitName?=?()?=>?{
??name?=?name.split('?');
}
name?=?splitName();
console.log(name);?//?outputs?[?'Yu',?'DongSu'?]
雖然上面的代碼看起來很合適。不是開玩笑。這是因?yàn)樵?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">splitName函數(shù)依賴于一個(gè)名為的外部變量name,如果其他人開始更改此變量,該函數(shù)將splitName開始提供不同的輸出。使它成為一個(gè)非純函數(shù),因?yàn)槲覀內(nèi)匀粫?huì)調(diào)用splitName()它,但輸出會(huì)有所不同。
讓我們把它改成一個(gè)純函數(shù),看看它是什么樣子的:
let?name?=?"Yu?DongSu";
const?splitName?=?(nameString)?=>?{
??return?nameString.split('?');
}
name?=?splitName(name);
console.log(name);?//?outputs?[?'Yu',?'DongSu'?]
通過上述更改,splitName現(xiàn)在是一個(gè)純函數(shù),因?yàn)椋?/p>
它僅依賴于輸入( nameString輸入)。它不會(huì)更改/重新分配任何外部變量
更少或命名參數(shù)
使用函數(shù)時(shí),我們經(jīng)常使用位置參數(shù),這些參數(shù)必須在函數(shù)聲明中聲明時(shí)提供。例如,在 call中,如果不提供and arithmaticOp(num1, num2, operator),我們就無法提供operator參數(shù)。雖然這適用于這個(gè)例子,但對于許多功能來說,這會(huì)成為一個(gè)問題。考慮以下示例:
const?createButton?=?(title,?color,?disabled,?padding,?margin,?border,?shadow)??=>?{
??console.log(title,?color,?disabled,?padding,?margin,?border,?shadow)
}
查看上面的代碼,您已經(jīng)可以看到,如果我們想在調(diào)用createButton+ 時(shí)將任何參數(shù)設(shè)為可選(使用默認(rèn)值),那將是一場災(zāi)難,可能看起來像這樣:
createButton('Sudongyu?er',?undefined?/*?optional?color?*/,?true?,'2px....',?undefined??/*?optional?margin*/);
你可以看到上面的語句看起來一點(diǎn)也不干凈。此外,從函數(shù)調(diào)用語句中很難看出哪個(gè)參數(shù)對應(yīng)于函數(shù)的哪個(gè)參數(shù)。所以這是我們可以遵循的做法:
如果我們有 2 個(gè)或更少的參數(shù),我們可以將它們保留為位置參數(shù) 否則,我們提供一個(gè)帶有鍵值對的對象
讓我們在上面的例子中使用這個(gè)技術(shù),看看它是什么樣子的:
const?createButton?=?({title,?color,?disabled,?padding,?margin,?border,?shadow})??=>?{
??console.log(title,?color,?disabled,?padding,?margin,?border,?shadow)
}
createButton({
??title:?'Sudongyu?er',
??disabled:?true,
??shadow:?'2px....'
});
請注意,調(diào)用createButton函數(shù)的語句現(xiàn)在更簡潔了。我們可以很容易地看到鍵值對中的哪個(gè)值對應(yīng)于函數(shù)的參數(shù)。耶耶!??
對象/數(shù)組解構(gòu)
考慮以下 javascript 示例,其中我們從對象中獲取一些屬性并分配給它們的各個(gè)變量:
const?user?=?{
??name:?'Sudongyu',
??email:?'hi@xxx',
??designation:?'Software?Architect',
??loves:?'The?Code'
}
const?name?=?user.name;
const?email?=?user.email;
const?loves?=?user.loves;
在上面的例子中,多次使用這個(gè)user.*符號是非常令人惡心的。這就是對象解構(gòu)的用武之地。我們可以使用對象解構(gòu)將上面的示例更改如下:
const?user?=?{
??name:?'Sudongyu',
??email:?'hi@xxx',
??designation:?'Software?Architect',
??loves:?'The?Code'
}
const?{name,?email,?loves}?=?user;
看!好多了。對?讓我們考慮另一個(gè)例子:
const?getDetails?=?()?=>?{
??return?['xxx',?'sudongyu',?'Some?Street',?'Some?City',?'Some?Zip',?'Some?Country'];
}
const?details?=?getDetails();
const?uName?=?details[0];
const?uEmail?=?details[1];
const?uAddress?=?`${details[2]},?${details[3]},?${details[4]},?${details[5]}`;
const?uFirstName?=?uName.split('?')[0];
const?uLastName?=?uName.split('?')[1];
啊。我什至討厭編寫上述示例的代碼??。雖然不得不這樣做。您可以看到代碼看起來非常怪異且難以閱讀。我們可以使用 Array Destructuring 將其寫得更簡潔,如下所示:
const?getDetails?=?()?=>?{
??return?['xxx',?'sudongyu',?'Some?Street',?'Some?City',?'Some?Zip',?'Some?Country'];
}
const?[uName,?uEmail,?...uAddressArr]?=?getDetails();
const?uAddress?=?uAddressArr.join(',?');
const?[uFirstName,?uLastName]?=?uName.split('');
console.log({
??uFirstName,
??uLastName,
??uEmail,
??uAddress
});
你可以看到這有多干凈??
避免硬編碼值
/**
?*?阿巴阿巴
?*?
?*?
?*?
?*?
?*?
?*/
setInterval(()?=>?{
??//?do?something
},?86400000);
//?WHAT?IS?THIS?86400000???????
看代碼的人可能不知道這個(gè)數(shù)字代表什么,它是如何計(jì)算的,以及它背后的業(yè)務(wù)邏輯是什么。我們可以創(chuàng)建一個(gè)常量,而不是硬編碼這個(gè)值,如下所示:
const?DAY_IN_MILLISECONDS?=?3600?*?24?*?1000;?//?86400000
setInterval(()?=>?{
??//?do?something
},?DAY_IN_MILLISECONDS);
//?now?this?makes?sense
讓我們考慮另一個(gè)例子:
const?createUser?=?(name,?designation,?type)?=>?{
??console.log({name,?designation,?type});
}
createUser('SudongYu',?'Software?Architect',?'1');
//?WHAT?IS?this?'1'????
查看調(diào)用createUser方法。閱讀代碼的人很難理解這'1'代表什么。即type這是什么用戶。因此,我們可以創(chuàng)建一個(gè)我們擁有的用戶類型的對象映射,而不是在這里對值進(jìn)行硬編碼'1',如下所示:
const?USER_TYPES?=?{
??REGULAR_EMPLOYEE:?'1'
}
const?createUser?=?(name,?designation,?type)?=>?{
??console.log({name,?designation,?type});
}
createUser('Sudongyu',?'Software?Architect',?USER_TYPES.REGULAR_EMPLOYEE);
//?smoooooooth???
避免使用簡寫變量名
速記變量在需要它們的地方才有意義。就像如果你有像xand這樣的位置坐標(biāo)y,那是可行的。p但是,如果我們在沒有上下文的情況下創(chuàng)建像,t之類的變量c,那么真的很難閱讀、跟蹤和維護(hù)這樣的代碼。例如看這個(gè)例子:
const?t?=?25;
let?users?=?['Sudongyuer',?'xxx'];
users?=?users.map((user)?=>?{
??/**
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*?
???*/
??return?{
????...user,
????tax:?user.salary?*?t?/?100?//?WHAT?IS?`t`?again????
??}
})
上面的例子表明,現(xiàn)在開發(fā)人員/讀者必須一直向上滾動(dòng)或轉(zhuǎn)到定義來嘗試?yán)斫膺@個(gè)變量是什么。因此是不干凈的代碼??。這也稱為對變量進(jìn)行思維導(dǎo)圖,其中只有作者知道它們的含義。因此,我們可以給它一個(gè)適當(dāng)?shù)拿Q,而不是簡寫變量名稱,如下所示:
const?taxFactor?=?25;
let?users?=?['Sudongyuer',?'xxx'];
users?=?users.map((user)?=>?{
??//?some?code
??return?{
????...user,
????tax:?user.salary?*?taxFactor?/?100
??}
})
現(xiàn)在這更有意義了。
使用 Object.assign() 設(shè)置默認(rèn)對象值
在某些情況下,您可能希望從另一個(gè)對象創(chuàng)建一個(gè)新對象,如果源對象沒有它們,則提供一些默認(rèn)值。考慮以下示例:
const?createButton?=?({title,?color,?disabled,?padding})??=>?{
??const?button?=?{};
??button.color?=?color?||?'#333';
??button.disabled?=?disabled?||?false;
??button.title?=?title?||?'';
??button.padding?=?padding?||?0;
??return?button;
}
const?buttonConfig?=?{
??title:?'Click?me!',
??disabled:?true
}
const?newButton?=?createButton(buttonConfig);
console.log('newButton',?newButton)
Object.assign()如果源對象提供了默認(rèn)屬性,我們可以使用如下方式覆蓋默認(rèn)屬性,而不是做所有這些:
const?createButton?=?(config)??=>?{
??return?{
????...{
??????color:?'#dcdcdc',
??????disabled:?false,
??????title:?'',
??????padding:?0
????},
????...config?
??};
}
const?buttonConfig?=?{
??title:?'Click?me!',
??disabled:?true
}
const?newButton?=?createButton(buttonConfig);
console.log('newButton',?newButton)
使用方法鏈(尤其是類)
如果我們知道類/對象的用戶將一起使用多個(gè)函數(shù),則方法鏈接是一種很有用的技術(shù)。您可能已經(jīng)在諸如 moment.js 之類的庫中看到了這一點(diǎn)。讓我們看一個(gè)例子:
class?Player?{
??constructor?(name,?score,?position)?{
????this.position?=?position;
????this.score?=?score;
????this.name?=?name;
??}
??setName(name)?{
????this.name?=?name;
??}
??setPosition(position)?{
????this.position?=?position;
??}
??setScore(score)?{
????this.score?=?score;
??}
}
const?player?=?new?Player();
player.setScore(0);
player.setName('Sudongyuer');
player..setPosition([2,?0]);
console.log(player)
在上面的代碼中,您可以看到我們需要為播放器一起調(diào)用一堆函數(shù)。如果您的對象/類是這種情況,請使用方法鏈接。您需要做的就是從要鏈接的函數(shù)中返回對象的實(shí)例。上面的例子可以修改如下來實(shí)現(xiàn):
class?Player?{
??constructor?(name,?score,?position)?{
????this.position?=?position;
????this.score?=?score;
????this.name?=?name;
??}
??setName(name)?{
????this.name?=?name;
????return?this;?//?<--?THIS
??}
??setPosition(position)?{
????this.position?=?position;
????return?this;?//?<--?THIS
??}
??setScore(score)?{
????this.score?=?score;
????return?this;?//?<--?THIS
??}
}
const?player?=?new?Player();
player.setScore(0).setName('Sudongyuer').setPosition([2,?0]);
//?SUPER?COOL???
console.log(player)
在回調(diào)上使用 Promise
承諾讓我們的生活更輕松。幾年前我們有一個(gè)叫做回調(diào)地獄的東西,這使得代碼很難閱讀。它看起來像這樣:

即使我正在使用具有回調(diào)的庫,我也會(huì)嘗試在那里添加一個(gè)包裝器來保證這一點(diǎn)(是的,現(xiàn)在這是一個(gè)術(shù)語)。讓我們考慮以下示例:
?const?getSocials?=?(callback)?=>?{
??setTimeout(()?=>?{
??????callback({socials:?{youtube:?'xxx',?twitter:?'xxx'}});
????},?1500);
}
const?getBooks?=?(callback)?=>?{
??setTimeout(()?=>?{
????callback({books:?['React?Cookbook']});
??},?1500);
}
const?getDesignation?=?(callback)?=>?{
??setTimeout(()?=>?{
????callback({designation:?'Software?Architect'});
??},?1500);
}
const?getUser?=?(callback)?=>?{
??setTimeout(()?=>?{
????callback({user:?'Sudongyuer'});
??},?1500);
}
?getUser(({user})?=>?{
????console.log('user?retrieved',?user)
????getDesignation(({designation})?=>?{
??????console.log('designation?retrieved',?designation)
??????getBooks(({books})?=>?{
????????console.log('books?retrieved',?books)
????????getSocials(({socials})?=>?{
??????????console.log('socials?retrieved',?socials)
????????})
??????})
????})
??})
上述代碼中的所有函數(shù)都是異步的,它們會(huì)在 1.5 秒后發(fā)回?cái)?shù)據(jù)。現(xiàn)在,如果涉及 15 個(gè)不同的功能,想象一下它會(huì)是什么樣子。可能就像我在上面分享的圖像??。為了更好的可讀性,我們可以promisify我們的函數(shù)并使用promises,而不是這個(gè)回調(diào)地獄:
const?getSocials?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({socials:?{youtube:?'xxx',?twitter:?'xxx'}});
????},?1500);
??})
}
const?getBooks?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({books:?['React?Cookbook']});
????},?1500);
??})
}
const?getDesignation?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({designation:?'Software?Architect'});
????},?1500);
??})
}
const?getUser?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({user:?'Sudongyuer'});
????},?1500);
??})
}
??getUser()
????.then(({user})?=>?{
??????console.log('user?retrieved',?user);
??????return?getDesignation();
????})
????.then(({designation})?=>?{
??????console.log('designation?retrieved',?designation)
??????return?getBooks();
????})
????.then(({books})?=>?{
??????console.log('books?retrieved',?books);
??????return?getSocials();
????})
????.then(({socials})?=>?{
??????console.log('socials?retrieved',?socials)
????})
您可以看到代碼現(xiàn)在已經(jīng)非常可讀,因?yàn)樗姓Z句都縮進(jìn)并顯示了在每個(gè)步驟.then()中檢索到的數(shù)據(jù)。.then()我們可以很容易地看到使用這種語法的步驟,因?yàn)槊看?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">.then()調(diào)用都會(huì)返回下一個(gè)函數(shù)調(diào)用及其承諾。
現(xiàn)在我們可以把它提升一個(gè)檔次,讓我們的代碼更具可讀性。如何?通過使用async await. 我們將修改我們的代碼如下來實(shí)現(xiàn):
const?getSocials?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({socials:?{youtube:?'xxx',?twitter:?'xxx'}});
????},?1500);
??})
}
const?getBooks?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({books:?['React?Cookbook']});
????},?1500);
??})
}
const?getDesignation?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({designation:?'Software?Architect'});
????},?1500);
??})
}
const?getUser?=?()?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(()?=>?{
??????resolve({user:?'Sudongyuer'});
????},?1500);
??})
}
const?performTasks?=?async?()?=>?{
??const?{user}?=?await?getUser();
??console.log('user?retrieved',?user);
??const?{designation}?=?await?getDesignation();
??console.log('designation?retrieved',?designation);
??const?{books}?=?await?getBooks();
??console.log('books?retrieved',?books);
??const?{socials}?=?await?getSocials();
??console.log('socials?retrieved',?socials);
}
請注意,我們將代碼包裝在performTasks()函數(shù)中,這是一個(gè)async函數(shù),您可以看到async關(guān)鍵字的用法。在內(nèi)部,我們使用await關(guān)鍵字進(jìn)行每個(gè)函數(shù)調(diào)用,這基本上會(huì)在執(zhí)行下一行代碼之前等待函數(shù)的承諾得到解決。使用這種語法,我們的代碼看起來好像都是同步的,但實(shí)際上是異步的。而且我們的代碼更干凈??
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我個(gè)小忙:
點(diǎn)個(gè)「喜歡」或「在看」,讓更多的人也能看到這篇內(nèi)容
我組建了個(gè)氛圍非常好的前端群,里面有很多前端小伙伴,歡迎加我微信「sherlocked_93」拉你加群,一起交流和學(xué)習(xí)
關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

