5 практических советов по разработке JavaScript

В этой статье мы поделимся 5 практическими навыками разработки на JavaScript!

1.Promise.all()、Promise.allSettled()

Мы можем использовать Promise, async/await для обработки асинхронных запросов. При одновременной обработке асинхронных запросов для этого можно использовать Promise.all() и Promise.allSettled().

Обещание.все()

Статический метод Promise.all() принимает итерируемый Promise в качестве входных данных и возвращает Promise. Когда все входные Promise будут выполнены, возвращенный Promise также будет выполнен (даже если передана пустая итерация), и будет возвращен массив, содержащий все выполненные значения. Если какое-либо из введенных обещаний будет отклонено, возвращенное обещание будет отклонено с указанием причины первого отклонения.

const promise1 = Promise.resolve(555);
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'foo'));
const promise3 = 23;

const allPromises = [promise1, promise2, promise3];
Promise.all(allPromises).then(values => console.log(values));

// 输出结果: [ 555, 'foo', 23 ]

Как вы можете видеть, когда все три промиса решены, Promise.all() разрешается и значение печатается. Но что, если один или несколько промисов будут отклонены и не будут решены?

const promise1 = Promise.resolve(555);
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.reject('rejected!');

const allPromises = [promise1, promise2, promise3];

Promise.all(allPromises)
  .then(values => console.log(values))
  .catch(err => console.error(err));

// 输出结果: rejected!

Promise.all() отклоняется, если отклонен хотя бы один из его элементов. В приведенном выше примере, если переданы два решенных промиса и один промис, который немедленно отклонен, то Promise.all() будет немедленно отклонен.

Обещание.allSettled()

Метод Promise.allSettled() был представлен в ES2020. Он принимает в качестве входных данных итерацию промисов и, в отличие от Promise.all(), возвращает промис, который всегда разрешается после того, как все данные промисы решены или отклонены. Промис разрешается с помощью массива объектов, описывающих результат каждого промиса.

Для результата каждого промиса существует два возможных состояния:

  • выполнено: значение, содержащее результат.
  • отклонено: содержит причину отклонения.
const promise1 = Promise.resolve(555);
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.reject('rejected!');

const allPromises = [promise1, promise2, promise3];

Promise.allSettled(allPromises)
  .then(values => console.log(values))

// 输出结果:
// [
//   { status: 'fulfilled', value: 555 },
//   { status: 'fulfilled', value: 'foo' },
//   { status: 'rejected', reason: 'rejected!' }
// ]

Так как же выбрать между двумя методами? Если вы хотите «быстро выйти из строя», вам следует выбрать Promise.all(). Рассмотрим сценарий, в котором вам нужно, чтобы все запросы были успешными, а затем определите некоторую логику, основанную на этом успехе. В этом случае быстрый сбой приемлем, поскольку после сбоя одного запроса результаты других запросов не имеют значения, и вы не хотите тратить ресурсы на оставшиеся запросы.

В остальных случаях желательно, чтобы все запросы либо отклонялись, либо разрешались. Promise.allSettled() — правильный выбор, если полученные данные используются для последующих задач или если вы хотите отображать и получать доступ к сообщениям об ошибках для каждого запроса.

2. Оператор объединения нулей: ??

Оператор объединения значений NULL возвращает правый операнд, если левый операнд имеет значение NULL или неопределен, в противном случае возвращает левый операнд. Это удобный синтаксис для получения значения первой «определенной» из двух переменных.

Например, результат x??y:

  • Возвращает x, если x не является нулевым или неопределенным.
  • Если x имеет значение NULL или не определено, возвращается y.

Итак, x??y можно записать как:

result = (x !== null && x !== undefined) ? x : y;

Обычно знак ?? используется для указания значения по умолчанию. Например, в следующем примере, если значение имени не равно нулю или не определено, отображается его значение, в противном случае отображается «Неизвестно»:

const name = someValue ?? "Unknown";
console.log(name);

Если someValue не равно нулю или не определено, то значением name будет someValue; если someValue равно null или неопределенно, тогда значением name будет «Неизвестно».

?? против ||

Логический оператор И (||) можно использовать так же, как и оператор объединения нулей (??). Вы можете заменить ?? на || и получить тот же результат, например:

let name;
console.log(name ?? "Unknown"); // 输出结果: Unknown
console.log(name || "Unknown"); // 输出结果: Unknown

Разница между ними заключается в следующем: || возвращает первое истинное значение. ?? Возвращает первое определенное значение (определенное = не пустое или неопределенное). То есть оператор || не различает значения false, 0, "" и null/undefined, которые все являются ложными значениями. Если какой-либо из них является первым аргументом ||, то результатом будет второй аргумент. Например:

let grade = 0;
console.log(grade || 100); // 输出结果: 100
console.log(grade ?? 100); // 输出结果: 0

Grade || 100 проверяет, является ли Grade ложным значением, в то время как его значение равно 0, что действительно является ложным значением. Таким образом, результатом || является второй аргумент, равный 100. И оценка ?? 100 проверяет, является ли оценка нулевой или неопределенной, но это не так, поэтому результат оценки равен 0.

Так как же выбрать между двумя методами?

  • Сценарии использования оператора объединения нулей (??):

Укажите значения по умолчанию для переменных. Если переменная может иметь значение NULL или неопределена, вы можете использовать оператор объединения значений NULL, чтобы предоставить для нее значение по умолчанию. Например: const name = inputName ?? "Unknown";

Обработка потенциально отсутствующих свойств: при доступе к свойствам объекта, если свойство может существовать, но имеет значение NULL или неопределенное, можно использовать оператор объединения NULL для предоставления значения по умолчанию. Например: const адрес = user.address??"Неизвестно";

Избегайте случаев ложных значений: оператор объединения значений Null можно использовать, когда мы хотим обрабатывать только явно определенные значения и избегать обработки ложных значений, таких как false, 0, пустая строка и т. д. Например: const value = userInputValue??0;

  • Используйте сценарии логического оператора ИЛИ (||):

Предоставление альтернативных значений. Когда нам нужно выбрать допустимое значение из нескольких вариантов, мы можем использовать логический оператор ИЛИ. Например: const result = значение1 || значение2 || значение3;

Условие принятия решения: когда нам нужно проверить, истинно ли какое-либо из нескольких условий, мы можем использовать логический оператор ИЛИ. Например: if (condition1 || условие2) { // выполняем операцию 

3. это

«Это» — понятие в JavaScript, которое часто неправильно понимают. Чтобы правильно использовать «это» в JavaScript, вам нужно хорошо понимать, как оно работает, поскольку оно имеет некоторые отличия от других языков программирования.

Вот пример распространенной ошибки при использовании «this»:

const obj = {
  helloWorld: "Hello World!",
  printHelloWorld: function () {
    console.log(this.helloWorld);
  },
  printHelloWorldAfter1Sec: function () {
    setTimeout(function () {
      console.log(this.helloWorld);
    }, 1000);
  },
};

obj.printHelloWorld();
// 输出结果: Hello World!

obj.printHelloWorldAfter1Sec();
// 输出结果: undefined

Первый результат выводит «Hello World!», поскольку this.helloWorld правильно указывает на свойство name объекта. Второй результат не определен, поскольку он потерял ссылку на свойство объекта. Это связано с тем, что то, к чему относится this, зависит от объекта, вызвавшего функцию, на которую оно помещено. В каждой функции есть переменная this, но объект, на который она указывает, определяется объектом, вызвавшим ее.

В obj.printHelloWorld() это напрямую указывает на obj. В obj.printHelloWorldAfter1Sec() это напрямую указывает на obj. Однако в функции обратного вызова setTimeout это не указывает ни на какой объект, поскольку нет объекта для его вызова. Используется объект по умолчанию (обычно окно). имя не существует в окне, поэтому возвращается неопределенное.

Чтобы правильно использовать это, вам нужно знать объект, с которым он связан при вызове функции. Если вы хотите получить доступ к свойствам объекта в функции обратного вызова, вы можете использовать стрелочные функции или явно привязать правильное значение this с помощью метода привязки(), чтобы избежать ошибок.

Как исправить эту проблему? Лучший способ сохранить эту ссылку в setTimeout — использовать стрелочные функции. В отличие от обычных функций, стрелочные функции не создают свои собственные this.

Поэтому следующий код будет содержать ссылку на это:

const obj = {
  helloWorld: "Hello World!",
  printHelloWorld: function () {
    console.log(this.helloWorld);
  },
  printHelloWorldAfter1Sec: function () {
    setTimeout(() => {
      console.log(this.helloWorld);
    }, 1000);
  },
};

obj.printHelloWorld();
// 输出结果: Hello World!

obj.printHelloWorldAfter1Sec();
// 输出结果: Hello World!

Вместо использования стрелочных функций есть другие способы решения этой проблемы.

  • Используйте метод связывания(): Метод связывания() создает новую функцию и возвращает значение после указания ее значения this. Вы можете использовать его для привязки функции к определенному объекту, гарантируя, что this всегда ссылается на этот объект.
  • Использование методов call() и apply(): эти два метода позволяют указать конкретное значение this для вызова функции. Разница между ними в том, что метод call() принимает в качестве аргумента массив значений, тогда как метод apply() принимает в качестве аргумента массив.
  • Использование переменной self: это был распространенный подход до появления стрелочных функций. Идея состоит в том, чтобы сохранить ссылку на это в переменной и использовать эту переменную внутри функции. Обратите внимание, что этот подход может не работать с вложенными функциями.

В целом каждый метод имеет свои преимущества и недостатки, и выбор того, какой метод использовать, зависит от конкретного сценария использования. В большинстве случаев функции стрелок рекомендуются по умолчанию.

4. Использование памяти

Иногда использование памяти приложением может быть очень плохим, см. следующий пример:

const data = [
  { name: 'Frogi', type: Type.Frog },
  { name: 'Mark', type: Type.Human },
  { name: 'John', type: Type.Human },
  { name: 'Rexi', type: Type.Dog }
];

Мы хотим добавить некоторые свойства к каждой сущности в зависимости от ее типа:

const mappedArr = data.map((entity) => {
  return {
    ...entity,
    walkingOnTwoLegs: entity.type === Type.Human
  }
});
// ...
const tooManyTimesMappedArr = mappedArr.map((entity) => {
  return {
    ...entity,
    greeting: entity.type === Type.Human ? 'hello' : 'none'
  }
});

console.log(tooManyTimesMappedArr);
// 输出结果:
// [
//   { name: 'Frogi', type: 'frog', walkingOnTwoLegs: false, greeting: 'none' },
//   { name: 'Mark', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'John', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'Rexi', type: 'dog', walkingOnTwoLegs: false, greeting: 'none' }
// ]

Как видите, с помощью карты можно выполнить простое преобразование и использовать его несколько раз. Для небольшого массива потребление памяти незначительно, но для более крупных массивов вы определенно обнаружите значительное влияние памяти.

Итак, каковы лучшие решения в этом случае?

Во-первых, нужно понимать, что при работе с большими массивами превышена пространственная сложность. Затем подумайте, как уменьшить потребление памяти. В этом примере есть несколько хороших вариантов:

1. Используйте карту в цепочке, чтобы избежать нескольких клонов:

const mappedArr = data
  .map((entity) => {
    return {
      ...entity,
      walkingOnTwoLegs: entity.type === Type.Human
    }
  })
  .map((entity) => {
    return {
      ...entity,
      greeting: entity.type === Type.Human ? 'hello' : 'none'
    }
  });

console.log(mappedArr);
// 输出结果:
// [
//   { name: 'Frogi', type: 'frog', walkingOnTwoLegs: false, greeting: 'none' },
//   { name: 'Mark', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'John', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'Rexi', type: 'dog', walkingOnTwoLegs: false, greeting: 'none' }
// ]

2. Лучший способ — уменьшить количество операций сопоставления и клонирования:

const mappedArr = data.map((entity) => 
  entity.type === Type.Human ? {
    ...entity,
    walkingOnTwoLegs: true,
    greeting: 'hello'
  } : {
    ...entity,
    walkingOnTwoLegs: false,
    greeting: 'none'
  }
);

console.log(mappedArr);
// 输出结果:
// [
//   { name: 'Frogi', type: 'frog', walkingOnTwoLegs: false, greeting: 'none' },
//   { name: 'Mark', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'John', type: 'human', walkingOnTwoLegs: true, greeting: 'hello' },
//   { name: 'Rexi', type: 'dog', walkingOnTwoLegs: false, greeting: 'none' }
// ]

5. Используйте карту или объект вместо переключателя.

Рассмотрим следующий пример:

function findCities(country) {
  switch (country) {
    case 'Russia':
      return ['Moscow', 'Saint Petersburg'];
    case 'Mexico':
      return ['Cancun', 'Mexico City'];
    case 'Germany':
      return ['Munich', 'Berlin'];
    default:
      return [];
  }
}

console.log(findCities(null));      // 输出结果: []
console.log(findCities('Germany')); // 输出结果: ['Munich', 'Berlin']

Приведенный выше код кажется нормальным, но того же результата можно достичь, используя более чистый синтаксис, используя объектные литералы:

const citiesCountry = {
  Russia: ['Moscow', 'Saint Petersburg'],
  Mexico: ['Cancun', 'Mexico City'],
  Germany: ['Munich', 'Berlin']
};

function findCities(country) {
  return citiesCountry[country] ?? [];
}

console.log(findCities(null));      // 输出结果: []
console.log(findCities('Germany')); // 输出结果: ['Munich', 'Berlin']

Map — это тип объекта, представленный в ES6, который позволяет хранить пары ключ-значение. Вы также можете использовать Map для достижения того же результата:

const citiesCountry = new Map()
  .set('Russia', ['Moscow', 'Saint Petersburg'])
  .set('Mexico', ['Cancun', 'Mexico City'])
  .set('Germany', ['Munich', 'Berlin']);

function findCities(country) {
  return citiesCountry.get(country) ?? [];
}

console.log(findCities(null));      // 输出结果: []
console.log(findCities('Germany')); // 输出结果: ['Munich', 'Berlin']

Так стоит ли нам прекратить использовать операторы переключения? нет. Использование объектных литералов или карт, где это возможно, может улучшить уровень кода и сделать его более элегантным.

Основные различия между литералами Map и объектными литералами заключаются в следующем:

  • Ключи.  В Картах ключи могут иметь любой тип данных (включая объекты и примитивные значения). В объектных литералах ключи должны быть строками или символами.
  • Итерация:  в Map ее можно повторять с помощью цикла for...of или метода forEach(). В литералах объектов вам нужно использовать Object.keys(), Object.values() или Object.entries() для итерации.
  • Производительность.  В целом карты работают лучше, чем литералы объектов, при работе с большими наборами данных или частыми добавлениями/удалениями. Для небольших наборов данных или нечастых операций разница в производительности незначительна. Выбор структуры данных для использования зависит от конкретного варианта использования. 

Supongo que te gusta

Origin blog.csdn.net/wangonik_l/article/details/132409140
Recomendado
Clasificación