10 приемов JS, о которых вы не знали

внешний интерфейс JavaScript
10 приемов JS, о которых вы не знали

Я Гарфилд 🐱, если вам понравилась моя статья, ставьте лайк и делитесь

Обобщите некоторые общие советы по разработке 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Также предусмотрен полифилл.

developer.Mozilla.org/en-US/docs/…

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:

Вавилон — это .IO/docs/en/daddy…

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

developer.Mozilla.org/en-US/docs/…

Обратите внимание, что с этим синтаксисом существуют проблемы совместимости (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');
}

Woohoo. Эта лошадь Plus.com/package/ava…

по факту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);

Ссылаться на

Способ сохранить мой код в чистоте

Вы можете использовать ES6, вот и все!

Разработчик подытожил эти 15 элегантных приемов JavaScript