Я Гарфилд 🐱, если вам понравилась моя статья, ставьте лайк и делитесь
Обобщите некоторые общие советы по разработке JS, чтобы сделать ваш код более элегантным!
1. Используйте константные определения
Не переусердствуйте с объявлением переменных при разработке и старайтесь использовать выражения и связанные вызовы. тогда обычно используютconst
не используйlet
. Написав много этого режима, вы обнаружите, что вряд ли сможете найти несколько приложений в проекте.let
Место:
// bad
let result = false;
if (userInfo.age > 30) {
result = true;
}
// good
const result = userInfo.age > 30;
Так напишут многие коллеги по проекту,
handleFormChange(e) {
let isUpload;
if (e.target.value === 'upload') {
isUpload = true;
} else {
isUpload = false;
}
}
Но по факту==
а также===
Выражение можно напрямую присвоить переменной:
handleFormChange(e) {
const isUpload = (e.target.value === 'upload');
}
Если вы хотите отрицать, это также очень просто:
handleFormChange(e) {
const isManual = (e.target.value !== 'upload');
}
2. Условно добавить свойства на объекты и массивы
1) Добавьте свойства на объект
Вы можете использовать оператор распространения для условного добавления свойств к объекту:
const condition = true;
const person = {
id: 1,
name: "dby",
...(condition && { age: 12 }),
};
если
condition
дляtrue
,но{ age: 16 }
будет добавлен к объекту; еслиcondition
дляfalse
, что эквивалентно расширениюfalse
, не окажет никакого влияния на объект
2) Добавить свойства в массив
Вот исходный код конфигурации Webpack в CRA:
module.exports = {
plugins: [
new HtmlWebpackPlugin(),
isEnvProduction &&
new MiniCssExtractPlugin(),
useTypeScript &&
new ForkTsCheckerWebpackPlugin(),
].filter(Boolean),
}
В приведенном выше коде только
isEnvProduction
дляtrue
будет добавленоMiniCssExtractPlugin
плагин; когдаisEnvProduction
дляfalse
добавитfalse
в массив, поэтому в конце используйтеfilter
фильтр
3. Доступ к последнему элементу массива
В разработке нам часто нужно получить доступ к последнему элементу массива.Традиционный подход таков:
const rollbackUserList = [1, 2, 3, 4];
const lastUser = rollbackUserList[rollbackUserList.length - 1]; // 4
Написать приведенную выше логику не составляет труда, но мы обнаружили, что когда имя переменной массива очень длинное, все выражение становится очень длинным, что влияет на читабельность кода. Один из способов — инкапсулировать приведенную выше логику:
// 将获取数组最后一个元素的逻辑封装为一个函数
const getLastEle = (arr) => arr[arr.length - 1];
const lastUser = getLastEle(rollbackUserList); // 4
Видно, что семантика лучше. Другой - использоватьArray.prototype.slice
метод, передавая отрицательный индекс, чтобы найти последний элемент справа налево:
const lastUser = rollbackUserList.slice(-1)[0]; // 4
// 或者使用解构赋值
const [lastUser] = rollbackUserList.slice(-1); // 4
Уведомлениеslice
метод возвращаетновый массивВместо самого элемента нужно вручную вынимать элемент из массива, что выглядит не очень изящно. На самом деле ES2022 специально предоставляетArray.prototype.at
метод для получения элемента массива на основе заданного индекса:
const lastUser = rollbackUserList.at(-1); // 4
at
Метод передает позиционирование слева направо, когда индекс положительный (это согласуется с эффектом прямого доступа к индексу), и позиционирование справа налево, когда индекс отрицательный. Очень полезно в сценариях, когда осуществляется доступ к последнему элементу массива. Поддерживается начиная с Chrome 92at
метод,core-js
Также предусмотрен полифилл.
4. Деструктурирующее назначение
Деструктурирующее назначение очень удобно и часто используется в проектах, его можно разделить на следующие два сценария:
- Деструктуризация объектов/массивов;
- деструктуризация параметров функции;
Вот четыре распространенных метода.
1) Глубокая деконструкция
В большинстве случаев мы будем деконструировать только один слой, но на самом деле присваивание деструктуризации может быть деструктировано глубоко:
let obj = {
name: "dby",
a: {
b: 1
}
}
const { a: { b } } = obj;
console.log(b); // 1
2) Используйте псевдонимы при деструктуризации
Если ключевое имя объекта, возвращаемое серверной частью, не то, что нам нужно, мы можем использовать псевдоним:
const obj = {
// 这个键名太长了,我们希望把它换掉
aaa_bbb_ccc: {
name: "dby",
age: 12,
sex: true
}
}
const { aaa_bbb_ccc: user } = obj;
console.log(user); // { name: "dby", age: 12, sex: true }
3) Использовать значения по умолчанию при деструктуризации
Деструктуризация объекта может использовать значение по умолчанию.Условие для того, чтобы значение по умолчанию вступило в силу, состоит в том, что значение свойства объекта строго равноundefined
:
fetchUserInfo()
.then(({ aaa_bbb_ccc: user = {} }) => {
// ...
})
Вышеуказанные три функции могут использоваться в сочетании
4) Используйте короткое замыкание, чтобы избежать ошибок
Хотя присваивание деструктурирования легко использовать, следует отметить, что разрушенный объект не может бытьundefined
,null
, иначе будет сообщено об ошибке, поэтому присвойте деконструированному объекту значение по умолчанию:
const {a,b,c,d,e} = obj || {};
5. Оператор спреда
Объедините два массива или два объекта с помощью оператора распространения:
const a = [1,2,3];
const b = [1,5,6];
// bad
const c = a.concat(b);
// good
const c = [...new Set([...a,...b])];
const obj1 = { a:1 };
const obj2 = { b:1 };
// bad
const obj = Object.assign({}, obj1, obj2);
// good
const obj = { ...obj1, ...obj2 };
Здесь есть проблема, на которую следует обратить внимание, хотя слияние объектов и массивов кажется и тем, и другим....
, а на самом деле разница есть.
Оператор распространения ES2015 указывает только то, чтомножествоа такжепараметр функциииспользуется в объектах, но не указывает, что его можно использовать в объектах, и основывается наfor...of
Таким образом, могут быть расширены только итерируемые объекты, такие как массивы, строки, наборы и карты.Если обычные объекты развернуты в массивы, будет сообщено об ошибке.
в объекте...
на самом деле синтаксис расширения объекта в ES2018, эквивалентныйObject.assign
:
6. Проверьте, существует ли свойство в объекте
можно использоватьin
Ключевое слово проверяет, существует ли свойство в объекте:
const person = { name: "dby", salary: 1000 };
console.log('salary' in person); // true
console.log('age' in person); // false
ноin
Ключевое слово на самом деле небезопасно и будет включать в себя свойства прототипа, например:
"hasOwnProperty" in {}; // true
"toString" in {}; // true
Поэтому для оценки рекомендуется использовать следующие методы:
Object.prototype.hasOwnProperty.call(person, "salary"); // true
Однако вышеприведенная задача слишком длинная, и очень хлопотно писать так каждый раз, когда вы ее используете. У ECMA есть новое предложениеObject.hasOwn()
, эквивалентноObject.prototype.hasOwnProperty.call()
псевдоним:
Object.hasOwn(person, "salary"); // true
Обратите внимание, что с этим синтаксисом существуют проблемы совместимости (Chrome > 92), но вы можете уверенно использовать его, если полифил настроен правильно.
7. Обход объектов
Многие коллеги по проекту напишут так:
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// ...
}
}
Тукао: использоватьObject.keys
илиObject.entries
Преобразованный в массив, вы можете использовать метод массива для обхода, и он проходит по своим собственным свойствам, а не по прототипу.
Object.keys(obj).forEach(key => {
// ...
})
Object.entries(obj).forEach(([key, val]) => {
// ...
})
Опровержение: иногда вы не хотите проходить весь объект, метод массива не может быть использован.break
Завершите цикл.
Тукао: Похоже, вы недостаточно хорошо владеете методами работы с массивами.find
Метод не продолжит обход, если найдет соответствующий элемент.
Object.keys(obj).find(key => key === "name");
Резюме: Старайтесь не писать циклы for в разработке, независимо от массивов и объектов. объект через
Object.keys
,Object.values
,Object.entries
Преобразование в массив для перемещения. Это можно записать как выражение JS, используя все преимущества функционального программирования.
8. Использование включает для упрощения суждений
Часто вижу такой код в проектах:
if (a === 1 || a === 2 || a === 3 || a === 4) {
// ...
}
можно использоватьincludes
Упрощенный код:
if ([1, 2, 3, 4].includes(a)) {
// ...
}
9. Два совета по Promise
1) асинхронная/ожидающая изящная обработка исключений
существуетasync
функционировать до тех пор, пока один изPromise
ошибка, всеasync
Выполнение функции прерывается, поэтому обработка исключений очень важна. Но по фактуasync
Обработка исключений функций очень хлопотна, и многие коллеги не хотят их писать. Есть простой способ сделать это? увидеть одинawait-to-js
Пакет npm для изящной обработкиasync
Исключение функции не нужно добавлять вручнуюtry...catch
Поймать исключение:
import to from 'await-to-js';
async function asyncFunctionWithThrow() {
const [err, user] = await to(UserModel.findById(1));
if (!user) throw new Error('User not found');
}
по фактуawait
ранее возвращенныйPromise
Он инкапсулирует слой для предварительной обработки исключений. Исходный код очень прост, и я реализовал его сам:
function to(awaited) {
// 不管是不是 Promise 一律转为 Promise
const p = Promise.resolve(awaited);
// await-to-js 采用 then...catch 的用法
// 但实际上 then 方法第一个回调函数里面并不包含会抛出异常的代码
// 因此使用 then 方法第二个回调函数捕获异常,不需要额外的 catch
return p.then(
res => {
return [null, res];
},
err => {
return [err, undefined];
}
)
}
2) Обещание как конечный автомат
Я видел, как коллега написал этот код:
function validateUserInfo(user) {
if (!userList.find(({ id }) => id === user.id)) {
return {
code: -1,
message: "用户未注册"
}
}
if (
!userList.find(
({ username, password }) =>
username === user.username &&
password === user.password
)
) {
return {
code: -1,
message: "用户名或密码错误"
}
}
return {
code: 0,
message: "登录成功"
}
}
Наблюдение обнаружило, что здесь на самом деле два состояния, а затем нужно поле, чтобы подсказать результат операции. В этом случае мы можем использоватьPromise
. Некоторые люди говорят, почему, очевидно, что асинхронной логики нет. мы знаем,Promise
По сути, это конечный автомат, даже если нам не нужно иметь дело с асинхронной логикой, мы можем использовать характеристики конечного автомата:
function validateUserInfo(user) {
if (!userList.find(({ id }) => id === user.id)) {
return Promise.reject("用户未注册");
}
if (
!userList.find(
({ username, password }) =>
username === user.username &&
password === user.password
)
) {
return Promise.reject("用户名或密码错误");
}
return Promise.resolve("登录成功");
}
// 使用如下
validateUserInfo(userInfo)
.then(res => {
message.success(res);
})
.catch(err => {
message.error(err);
})
Очевидно, что это делает код очень элегантным, но он мог бы быть и более элегантным. мы знаемasync
Возвращаемое значение функции является экземпляром Promise, поэтому следующие две функции эквивалентны:
// 普通函数返回一个 Promsie.resolve 包裹的值
const request = (x) => Promise.resolve(x);
// async 函数返回一个值
const request = async (x) => x;
Поскольку он, наконец, возвращаетPromise
, почему бы не добавить его прямо перед функциейasync
А модификаторы? Таким образом, успешный результат нужно возвращать только напрямую, нет необходимостиPromise.resolve
пакет:
async function validateUserInfo(user) {
if (!userList.find(({ id }) => id === user.id)) {
return Promise.reject("用户未注册");
}
if (
!userList.find(
({ username, password }) =>
username === user.username &&
password === user.password
)
) {
return Promise.reject("用户名或密码错误");
}
return "登录成功";
}
правильно
async
Учащиеся, не знакомые с функциями, могут обратиться кРуан Ифэн ES6 Учебник
Идем дальше, поскольку вPromise
Внутреннее генерирование исключения эквивалентно тому, чтобы бытьreject
, поэтому мы можем использоватьthrow
замена оператораPromise.reject()
:
async function validateUserInfo(user) {
if (!userList.find(({ id }) => id === user.id)) {
throw "用户未注册";
}
if (
!userList.find(
({ username, password }) =>
username === user.username &&
password === user.password
)
) {
throw "用户名或密码错误";
}
return "登录成功";
}
throw
Использование предложения может относиться кДокументация MDN
10. В строке меньше двух цифр, заполненных нулями
Это требование довольно распространено в разработке. Например, дата, полученная вызовом API Date, может состоять только из одной цифры:
let date = new Date().getDate(); // 3
Общая практика:
if (data.toString().length == 1) {
date = `0${date}`;
}
использоватьString.prototype.slice
:
// 不管几位,都在前面拼接一个 0 ,然后截取最后两位
date = `0${date}`.slice(-2);
использоватьString.prototype.padStart
:
// 当字符串长度小于第一个参数值,就在前面补第二个参数
date = `${date}`.padStart(2, 0);
Ссылаться на
Способ сохранить мой код в чистоте