12 концепций JavaScript, о которых нужно знать

Автор оригинала: Nick Scialli
  • Перевод
  • Tutorial
JavaScript — это сложный язык. Если вы, на любом уровне, занимаетесь JavaScript-разработкой, это значит, что вам жизненно необходимо понимать базовые концепции этого языка. В материале, перевод которого мы сегодня публикуем, рассмотрены 12 важнейших концепций JavaScript. Конечно, JavaScript-разработчику нужно знать гораздо больше, но без того, о чём мы будем сегодня говорить, ему точно не обойтись.



1. Переменные, хранящие значения и ссылки


Понимание того, как именно в JavaScript назначаются значения переменных крайне важно для тех, кто хочет писать правильно работающий код. Непонимание этого механизма ведёт к написанию программ, в которых значения переменных могут непреднамеренно меняться.

JavaScript, если некая сущность имеет один из примитивных типов (в частности — это типы Boolean, null, undefined, String и Number), всегда работает со значением этой сущности. То есть, в соответствующую переменную записывается именно значение. Если же речь идёт об объекте (это, например, типы Object, Array, Function), то, при назначении его переменной, в неё записывается ссылка на него, адрес, по которому он расположен в памяти.

Рассмотрим пример. В следующем фрагменте кода в переменную var1 записана строка. После этого в переменную var2 записано значение переменной var1. Так как переменная var1 имеет примитивный тип (String), то в var2 будет записана копия строки, имеющейся в var1. Это позволяет рассматривать var2 как переменную, полностью независимую от var1, хотя и хранящую то же значение, что и var1. Запись в var2 нового значения на var1 не влияет.

let var1 = 'My string';
let var2 = var1;
var2 = 'My new string';
console.log(var1);
// 'My string'
console.log(var2);
// 'My new string'

Теперь рассмотрим пример работы с объектами.

let var1 = { name: 'Jim' }
let var2 = var1;
var2.name = 'John';
console.log(var1);
// { name: 'John' }
console.log(var2);
// { name: 'John' }

Как видите, здесь мы работаем с переменной var2, а то, что с ней происходит, отражается и на переменной var1 так как они хранят ссылку на один и тот же объект. Несложно представить себе к чему подобное может привести в реальном коде в том случае, если некто решит, что переменные, хранящие объекты, ведут себя так же, как переменные, хранящие значения примитивных типов. Особенно это неприятно, например, в случаях, когда создают функцию, которая рассчитана на работу с переданным ей объектным значением, и эта функция данное значение непреднамеренно изменяет.

2. Замыкания


Замыкание — это важный паттерн проектирования в JavaScript, который позволяет организовать защищённую работу с переменными. В следующем примере функция createGreeter() возвращает анонимную функцию, у которой есть доступ к предоставленному исходной функции аргументу greeting, содержащему строку Hello. Ссылка на эту анонимную функцию записывается в переменную sayHello. После этого, сколько раз бы мы ни вызывали функцию sayHello(), у неё всегда будет доступ к значению greeting. При этом доступ к greeting будет только у анонимной функции, ссылка на которую записана в sayHello.

function createGreeter(greeting) {
  return function(name) {
    console.log(greeting + ', ' + name);
  }
}
const sayHello = createGreeter('Hello');
sayHello('Joe');
// Hello, Joe

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

function apiConnect(apiKey) {
  function get(route) {
    return fetch(`${route}?key=${apiKey}`);
  }
  function post(route, params) {
    return fetch(route, {
      method: 'POST',
      body: JSON.stringify(params),
        headers: {
          'Authorization': `Bearer ${apiKey}`
        }
      })
  }
  return { get, post }
}
const api = apiConnect('my-secret-key');
// Использовать ключ доступа к API больше уже не нужно
api.get('http://www.example.com/get-endpoint');
api.post('http://www.example.com/post-endpoint', { name: 'Joe' });

3. Деструктурирующее присваивание


Если вы до сих пор не пользовались деструктурирующим присваиванием в JavaScript, то это пора исправить. Деструктурирующее присваивание представляет собой распространённый способ извлечения свойств объектов с использованием аккуратной синтаксической конструкции языка.

const obj = {
  name: 'Joe',
  food: 'cake'
}
const { name, food } = obj;
console.log(name, food);
// 'Joe' 'cake'

Если извлечённым свойствам нужно присвоить имена, отличающиеся от тех, которые они имеют в объекте, можно поступить так:

const obj = {
  name: 'Joe',
  food: 'cake'
}
const { name: myName, food: myFood } = obj;
console.log(myName, myFood);
// 'Joe' 'cake'

В следующем примере деструктурирование используется для аккуратной передачи значений, хранящихся в свойствах объекта person, функции introduce(). Это — пример того, как данная конструкция используется при объявлении функции для извлечения данных из переданного ей объекта с параметрами. Кстати, если вы знакомы с React, то вы, вероятно, уже такое видели.

const person = {
  name: 'Eddie',
  age: 24
}
function introduce({ name, age }) {
  console.log(`I'm ${name} and I'm ${age} years old!`);
}
console.log(introduce(person));
// "I'm Eddie and I'm 24 years old!"

4. Оператор spread


Оператор spread — это довольно простая конструкция, которая может показаться неподготовленному человеку непонятной. В следующем примере есть числовой массив, максимальное значение, хранящееся в котором, нам нужно найти. Мы хотим использовать для этого метод Math.max(), но он с массивами работать не умеет. Он, в качестве аргументов, принимает самостоятельные числовые значения. Для того чтобы извлечь из массива его элементы мы используем оператор spread, который выглядит как три точки.

const arr = [4, 6, -1, 3, 10, 4];
const max = Math.max(...arr);
console.log(max);
// 10

5. Оператор rest


Оператор rest позволяет преобразовывать любое количество аргументов, переданных функции, в массив.

function myFunc(...args) {
  console.log(args[0] + args[1]);
}
myFunc(1, 2, 3, 4);
// 3

6. Методы массивов


Методы массивов часто дают разработчику удобные инструменты, позволяющие красиво решать самые разные задачи по преобразованию данных. Я иногда отвечаю на вопросы на StackOverflow. Среди них часто попадаются такие, которые посвящены чему-то вроде тех или иным способов работы с массивами объектов. Именно в таких ситуациях методы массивов особенно полезны.

Здесь мы рассмотрим несколько таких методов, объединённых по принципу их схожести друг с другом. Надо отметить, что тут я расскажу далеко не обо всех методах массивов. Найти их полный список можно на MDN (кстати, это — мой любимый справочник по JavaScript).

▍Методы map(), filter() и reduce()


Методы массивов map(), filter() и reduce() позволяют трансформировать массивы или сводить массивы к одному значению (которое может быть объектом).

Метод map() возвращает новый массив, содержащий трансформированные значения обрабатываемого массива. То, как именно они будут трансформированы, задаётся в передаваемой этому методу функции.

const arr = [1, 2, 3, 4, 5, 6];
const mapped = arr.map(el => el + 20);
console.log(mapped);
// [21, 22, 23, 24, 25, 26]

Метод filter() возвращает массив элементов, проверяя значения которых функция, переданная этому методу, возвратила true.

const arr = [1, 2, 3, 4, 5, 6];
const filtered = arr.filter(el => el === 2 || el === 4);
console.log(filtered);
// [2, 4]

Метод reduce() возвращает некое значение, представляющее собой результат обработки всех элементов массива.

const arr = [1, 2, 3, 4, 5, 6];
const reduced = arr.reduce((total, current) => total + current);
console.log(reduced);
// 21

▍Методы find(), findIndex() и indexOf()


Методы массивов find(), findIndex() и indexOf() легко перепутать друг с другом. Ниже даны пояснения, помогающие понять их особенности.

Метод find() возвращает первый элемент массива, соответствующий заданному критерию. Этот метод, найдя первый подходящий элемент, не продолжает поиск по массиву.

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const found = arr.find(el => el > 5);
console.log(found);
// 6

Обратите внимание на то, что в нашем примере заданному критерию соответствуют все элементы массива, следующие за тем, который содержит число 5, но возвращается лишь первый подходящий элемент. Этот метод весьма полезен в ситуациях, в которых, пользуясь для перебора и анализа массивов циклами for, такие циклы, при обнаружении в массиве нужного элемента, прерывают, используя инструкцию break.

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

const arr = ['Nick', 'Frank', 'Joe', 'Frank'];
const foundIndex = arr.findIndex(el => el === 'Frank');
console.log(foundIndex);
// 1

Метод indexOf() очень похож на метод findIndex(), но он принимает в качестве аргумента не функцию, а обычное значение. Использовать его можно в том случае, если при поиске нужного элемента массива не нужна сложная логика.

const arr = ['Nick', 'Frank', 'Joe', 'Frank'];
const foundIndex = arr.indexOf('Frank');
console.log(foundIndex);
// 1

▍Методы push(), pop(), shift() и unshift()


Методы push(), pop(), shift() и unshift() применяются для добавления в массивы новых элементов и для извлечения из массивов уже имеющихся в них элементов. При этом работа производится с элементами, находящимися в начале или в конце массива.

Метод push() позволяет добавлять элементы в конец массива. Он модифицирует массив, и, после завершения работы, возвращает элемент, добавленный в массив.

let arr = [1, 2, 3, 4];
const pushed = arr.push(5);
console.log(arr);
// [1, 2, 3, 4, 5]
console.log(pushed);
// 5

Метод pop() удаляет из массива последний элемент. Он модифицирует массив и возвращает удалённый из него элемент.

let arr = [1, 2, 3, 4];
const popped = arr.pop();
console.log(arr);
// [1, 2, 3]
console.log(popped);
// 4

Метод shift() удаляет из массива первый элемент и возвращает его. Он тоже модифицирует массив, для которого его вызывают.

let arr = [1, 2, 3, 4];
const shifted = arr.shift();
console.log(arr);
// [2, 3, 4]
console.log(shifted);
// 1

Метод unshift() добавляет один или большее количество элементов в начало массива. Он, опять же, модифицирует массив. При этом, в отличие от трёх других рассмотренных здесь методов, он возвращает новую длину массива.

let arr = [1, 2, 3, 4];
const unshifted = arr.unshift(5, 6, 7);
console.log(arr);
// [5, 6, 7, 1, 2, 3, 4]
console.log(unshifted);
// 7

▍Методы slice() и splice()


Эти методы используются для модификации массива или для возврата некоей части массива.

Метод splice() меняет содержимое массива, удаляя существующие элементы или заменяя их на другие элементы. Он умеет и добавлять в массив новые элементы. Этот метод модифицирует массив.

Следующий пример, если описать его обычным языком, выглядит так: нужно, в позиции массива 1, удалить 0 элементов и добавить элемент, содержащий b.

let arr = ['a', 'c', 'd', 'e'];
arr.splice(1, 0, 'b')

Метод slice() возвращает неглубокую копию массива, содержащую его элементы, начиная с заданной начальной позиции и заканчивая позицией, предшествующей заданной конечной позиции. Если при его вызове задана только начальная позиция, то он вернёт весь массив, начиная с этой позиции. Этот метод не модифицирует массив. Он лишь возвращает описанную при его вызове часть этого массива.

let arr = ['a', 'b', 'c', 'd', 'e'];
const sliced = arr.slice(2, 4);
console.log(sliced);
// ['c', 'd']
console.log(arr);
// ['a', 'b', 'c', 'd', 'e']

▍Метод sort()


Метод sort() выполняет сортировку массива в соответствии с условием, заданным переданной ему функцией. Эта функция принимает два элемента массива (например, они могут быть представлены в виде параметров a и b), и, сравнивая их, возвращает, в том случае, если элементы менять местами не надо, 0, если a нужно поставить по меньшему индексу, чем b — отрицательное число, а если b нужно поставить по меньшему индексу, чем a — положительное число.

let arr = [1, 7, 3, -1, 5, 7, 2];
const sorter = (firstEl, secondEl) => firstEl - secondEl;
arr.sort(sorter);
console.log(arr);
// [-1, 1, 2, 3, 5, 7, 7]

Если вы не можете, впервые ознакомившись с этими методами, их запомнить — ничего страшного. Самое главное это то, что вы теперь знаете о том, что умеют стандартные методы массивов. Поэтому, если вы и не можете сходу вспомнить особенности того или иного метода, то, что вы о нём знаете, позволит вам быстро найти то, что нужно, в документации.

7. Генераторы


Генераторы в JavaScript объявляют, используя символ звёздочки. Они позволяют задавать то, какое значение будет возвращено при очередном вызове метода next(). Генераторы могут быть рассчитаны на возврат ограниченного количества значений. Если подобный генератор возвратил все такие значения, то очередной вызов next() вернёт undefined. Можно создавать и генераторы, рассчитанные на возврат неограниченного количества значений с использованием циклов.

Вот генератор, рассчитанный на возврат ограниченного числа значений:

function* greeter() {
  yield 'Hi';
  yield 'How are you?';
  yield 'Bye';
}
const greet = greeter();
console.log(greet.next().value);
// 'Hi'
console.log(greet.next().value);
// 'How are you?'
console.log(greet.next().value);
// 'Bye'
console.log(greet.next().value);
// undefined

А вот генератор, рассчитанный на возврат бесконечного количества значений посредством цикла.

function* idCreator() {
  let i = 0;
  while (true)
    yield i++;
}
const ids = idCreator();
console.log(ids.next().value);
// 0
console.log(ids.next().value);
// 1
console.log(ids.next().value);
// 2
// и так далее...

8. Операторы проверки равенства (==) и строгого равенства (===) значений


Любому JS-разработчику чрезвычайно важно понимать разницу между операторами равенства (==) и строгого равенства (===). Дело в том, что оператор ==, перед сравнением значений, выполняет преобразование их типов (что может приводить к странным, на первый взгляд, последствиям), а оператор === преобразование типов не производит.

console.log(0 == '0');
// true
console.log(0 === '0');
// false

9. Сравнение объектов


Мне периодически приходится видеть, как новички в JS-программировании совершают одну и ту же ошибку. Они пытаются напрямую сравнивать объекты. Переменные, в которых «хранятся» объекты, содержат в себе ссылки на них, а не сами эти объекты.

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

const joe1 = { name: 'Joe' };
const joe2 = { name: 'Joe' };
console.log(joe1 === joe2);
// false

При этом в следующем примере оказывается, что joe1 равно joe2 так как обе переменные хранят ссылку на один и тот же объект.

const joe1 = { name: 'Joe' };
const joe2 = joe1;
console.log(joe1 === joe2);
// true

Один из методов настоящего сравнения объектов заключается в их предварительном преобразовании в формат JSON-строк. Правда, у такого подхода есть одна проблема, которая заключается в том, что в полученном строковом представлении объекта не гарантируется определённый порядок следования его свойств. Более надёжный способ сравнения объектов заключается в использовании специальной библиотеки, содержащей средства для глубокого сравнения объектов (например — это метод isEqual() библиотеки lodash).

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

10. Функции обратного вызова


Функции обратного вызова — это довольно простая концепция JavaScript, с которой у новичков иногда возникают сложности. Рассмотрим следующий пример. Здесь функция console.log (именно так — без скобок) передаётся функции myFunc() в качестве функции обратного вызова. Эта функция устанавливает таймер, по срабатыванию которого вызывается console.log() и переданная функции myFunc() строка выводится в консоль.

function myFunc(text, callback) {
  setTimeout(function() {
    callback(text);
  }, 2000);
}
myFunc('Hello world!', console.log);
// 'Hello world!'

11. Промисы


После того, как вы освоите функции обратного вызова и начнёте всюду их использовать, вы очень скоро можете обнаружить себя в так называемом «аду коллбэков». Если вы и правда там оказались — взгляните на промисы. Асинхронный код можно обернуть в промис, и, после его успешного выполнения, сообщить системе об успешном разрешении промиса, вызвав соответствующий метод, а если что-то пойдёт не так — вызвать метод, указывающий на это и отклонить промис. Для того чтобы обработать результаты, возвращаемые промисом, воспользуйтесь методом then(), а для обработки ошибок — методом catch().

const myPromise = new Promise(function(res, rej) {
  setTimeout(function(){
    if (Math.random() < 0.9) {
      return res('Hooray!');
    }
    return rej('Oh no!');
  }, 1000);
});
myPromise
  .then(function(data) {
    console.log('Success: ' + data);
   })
   .catch(function(err) {
    console.log('Error: ' + err);
   });
   
// Если Math.random() вернёт значение, меньшее, чем 0.9, в консоль попадёт следующее:
// "Success: Hooray!"
// Если Math.random() вернёт значение, большее, чем 0.9, или 0.9, в консоль попадёт следующее:
// "Error: On no!"

12. Конструкция async/await


После того, как вы поработаете с промисами, то вам, вполне возможно, захочется чего-то большего. Например — освоить конструкцию async/await. Она представляет собой «синтаксический сахар» для промисов. В следующем примере мы создаём, с помощью ключевого слова async, асинхронную функцию, и в ней, пользуясь ключевым словом await, организуем ожидание выполнения промиса greeter.

const greeter = new Promise((res, rej) => {
  setTimeout(() => res('Hello world!'), 2000);
})
async function myFunc() {
  const greeting = await greeter;
  console.log(greeting);
}
myFunc();
// 'Hello world!'

Итоги


Если то, о чём мы здесь говорили, было вам до этого незнакомо, то вы, скорее всего, хотя бы немного, но выросли над собой, прочитав эту статью. Если же вы не нашли тут ничего нового, тогда хочется надеяться, что этот материал дал вам возможность попрактиковаться и укрепить ваши знания JavaScript.

Уважаемые читатели! Какие ещё концепции JavaScript вы добавили бы в эту статью?

RUVDS.com
1 001,39
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Похожие публикации

Комментарии 61

    +35
    инструкция, как быстро написать статью о JS, когда писать не о чем:
    1. копипастим из учебника 12 случайных глав. каждую главу сокращаем до 5 предложений.
    2. пишем, что это очень крутые концепции (операторы — это концепции), о которых мало кто знает, но без которых прогу не написать, даже не пытайтесь!
    3. PROFIT
      +14
      3 концепции написания статей про JavaScript, о которых нужно знать
        0
        это, например, типы Object, Array, Function

        В javascript нет типов Array и Function. Есть Object.
        Я бы перефразировал эту часть предложения.
        Иначе это введет в заблуждение начинающих js'еров.
          0

          Ну, что-то напоминающее эти типы всё-таки есть.


          "Объект типа Array" — это exotic object, ведущий себя как массив. Отличается от объекта переопределенным слотом [[DefineOwnProperty]], который корректирует length при добавлении элементов.


          "Объект типа Function" — это объект с определёнными слотами [[Call]] и [[Construct]]

        +1
        по крайней мере такие статьи полезны в плане «повторение — мать учения»
        хотя для опытных разработчиков они могут показаться утомительными и разочаровывающими
          +1
          4. Впихиваем рекламу своего продукта. Ведь ради этого написана статья, не так ли?
          +3
          Что стоит добавить — это в каких стандартах языка всё это работает. Статья явно рассчитана на не очень опытного разработчика, и может запутать в этом смысле.
            0

            Согласен с вами, меня удивила destructuring assignment, не во всех браузерах это поддерживается. Может я не прав?

              +1
              Destructuring assignment довольно старая фича, ей уже четыре года (появилась в ES6). Сегодня уже поддерживается всеми основными браузерами (кроме тех, что делает MS)&
            +2
            Оператор rest

            Это не оператор. Это синтаксис. MDN
              +6
              push() позволяет добавлять элементы в конец массива. <...> после завершения работы, возвращает элемент, добавленный в массив.

              <...> в отличие от трёх других рассмотренных здесь методов, unshift() возвращает новую длину массива.
              Ну неправда же. Хоть бы посмотрели на своём любимом MDN.

              [].push(10); // => 1
                0
                Вопрос слегка не по теме (но напомнило описанием методов reduce() и splice()).
                Когда-то давно меня очень удивили методы reduceRight() и copyWithin(). Прошли годы, а я так и не столкнулся ни с одним случаем их применения. :)
                Было бы интересно узнать о таких, если кто-то знает.

                Краткий бриф для тех, кому лень идти в MDN
                Метод Array#reduceRight() полностью идентичен методу Array#reduce(), но (как можно догадаться) обходит массив в обратном порядке — от последнего элемента до первого.
                [1, 2, 3].reduceRight((sum, x) => {
                    console.log(x);
                    return sum + x;
                }, 0);   // выводит 3, 2, 1; возвращает 6 


                Метод Array#copyWithin() копирует участок (slice) массива в него же, начиная с указанной позиции. MDN сравнивает его с сишной memmove().
                let arr = [1, 2, 3, 4, 5, 6];
                arr.copyWithin(/* target = */ 1, /* start = */ 4, /* end = */ 6);
                // arr теперь [1, 5, 6, 4, 5, 6] 


                  0
                  Единственный раз, когда использовал reduceRight(), был на codewars. Кажется там именно «right» позволял написать код лаконичнее. Но подробностей, увы, не помню.
                    +1
                    copyWithin() появилась в эпоху asm.js для быстрого копирования блока байтов внутри одного ArrayBuffer (который в asm.js и wasm выступает в качестве кучи).

                    пример с copyWithin:
                    heap.copyWithin(target, start, end)


                    пример без copyWithin:
                    heap.set(new Uint8Array(heap.buffer, start, end - start), target)

                    много времени теряется на создание Uint8Array (особенно в Firefox и Edge) в ситуации, когда нужно копировать большое количество мелких блоков.
                      0
                      Спасибо, хорошее объяснение необходимости copyWithin() и наглядный пример.
                      Его, в целом можно обобщить — copyWithin() позволяет реализовать быструю фильтрацию массива in-place. :)
                        0
                        это не фильтрация
                          –2
                          Выбрать в массиве все элементы, удовлетворяющие некоторому условию, переместить их в начало массива и вернуть новую эффективную длину массива — это не фильтрация?
                          Ну да, абсолютно точно не в том смысле, как функциональный filter(). Поэтому я использовал (придуманный на ходу) термин «фильтрация in-place», как мне кажется, он неплохо передаёт суть. Есть более общепринятый термин для такого алгоритма?
                            +1
                            Выбрать в массиве все элементы, удовлетворяющие некоторому условию

                            какое условие? нужно читать описание метода, а не слушать голоса в своей голове.
                              +1
                              Вы, видимо, не очень внимательно прочитали мой комментарий.

                              Я ничего не писал про условие в copyWithin(), я написал что этот метод "позволяет реализовать быструю фильтрацию массива in-place" — собственно, так же, как это делает asm.js при реализации кучи.

                              Само собой, copyWithin() будет только частью такого алгоритма — реализуя перемещение непрерывных последовательностей элементов, удовлетворяющих условию, в начало массива.
                      0
                      reduceRight полезен, если нам нужно пройти по массиву, и при этом некоторые его элементы могут быть в этом процессе выкинуты.
                        0
                        А можете чуть более конкретный пример привести? Я пока не очень понимаю, о чём речь. :(
                          0
                          Ну, вот у меня в одном случае был массив данных, каждый элемент в котором представлял собой объект, который мог нести пометку, что в некоторый момент он должен быть удалён. И вот наступает этот момент. Решением «в лоб» было использование метода массивов filter, но он не фильтрует массив «на месте», он его пересоздаёт, следовательно на свойстве, которому он присвоен, сработает сеттер, а проверки на равенство с другими свойствами, где ссылка на него была сохранена ранее, дадут false.
                          И вот тут оказался как нельзя кстати reduceRight. Конечно, есть и другие варианты, но этот, возможно, самый лаконичный. Перебор, устойчивый к выкидыванию элементов в процессе.
                            0
                            Перебор, устойчивый к выкидыванию элементов в процессе.
                            Ох, возможно, он и лаконичный, но мне лично пришлось немного поломать голову. Как-то я никогда не воспринимал так reduceRight(). :)

                            Насколько я понимаю, код в целом был примерно такой:
                            const array = [
                                { value: 1, removed: false },
                                { value: 2, removed: false },
                                { value: 3, removed: true  },
                                { value: 4, removed: false }
                            ];
                            
                            array.reduceRight((_, value, index, array) => {
                                console.log(value);  
                            
                                if (value.removed) {
                                    array.splice(index, 1);
                                }
                            }, null);

                              0
                              Похоже на ещё одну вариацию алгоритма маляра Шлемиэля…
                                0
                                Да, я тоже об этом подумал, пока писал.
                                По-хорошему надо удалять непрерывными последовательностями — например через copyWithin(), чтобы было комбо. :)
                                Но мне важнее было получить простейший и наглядный пример.
                                  0
                                  let n = array.length
                                  while (n--) {
                                    if (array[n].removed) {
                                      const swap = array.pop()
                                      if (array.length != n)
                                        array[n] = swap
                                    }
                                  }

                                  Так будет эффективнее, хотя порядок элементов изменится.
                                    0
                                    Хитро сделано. В очередной раз убеждаюсь, что обмен элементов — очень мощная техника при работе с массивами. :)

                                    В целом, ничто не мешает аналогичный код написать внутри reduceRight() — если в процессе обработки нужно свернуть массив. Но это уже дело вкуса.
                                      0
                                      По-хорошему, внутрь reduceRight нужно передавать чистую функцию.
                                        0
                                        Спорное утверждение в языке с отсутствием иммутабельности. Вообще, изначально идея удалять элементы в reduceRight() была torbasow, вопросы к нему. :D

                                        Но мне субъективно проще воспринимать алгоритм через reduce (пусть его коллбэк и не чистая функция), чем через while.
                        0
                        Пример reduceRight() — работа с порядком элементов в списках, т.е. банально иногда удобнее справа налево (оно же с последнего до первого.)
                          0
                          Да, это самый популярный пример.

                          На мой взгляд, он, наоборот, сложнее для понимания, и вариант через reduce() гораздо читабельнее (именование аргументов ещё решает):
                          ['1', '2', '3', '4', '5'].reduce((res, value) => value + res); 
                          

                          Вероятно, это просто дело вкуса.
                            0
                            Это когда оператор коммутативен (value + res === res + value). А если нет? Мало ли какие могут быть варианты.
                              0
                              Эм, так конкатенация строк как раз-таки не коммутативна.
                          +1
                          {
                          const Node = (next, value) => ({next, value})
                          const array = [1, 2, 3, 4]
                          
                          // create a linked list from the array
                          const listA = array.slice().reverse().reduce(Node, null)
                          
                          // a better way of doing it:
                          const listB = array.reduceRight(Node, null)
                          }
                          
                            0
                            Ух ты, какой отличный пример! Спасибо!
                              0
                              Тут надо иметь в виду, что вот как раз reverse переворачивает массив «на месте», то есть способ с его использованием может оказаться нежелателен, если мы хотим, чтобы в переменной array порядок сохранился.
                                0
                                Ага, то есть тут reduceRight() прямо максимально к месту.

                                Плюс мне очень понравился сам способ построения списка из массива — лаконично и выразительно, прямо очень sexy. :)
                          0
                          Методы find(), findIndexOf() и indexOf()

                          В JS нет метода findIndexOf().

                            +1
                            Исправили опечатку
                            0

                            Я бы добавил раздел про mutable ("изменчивость"?), Array.concat(), Object.keys(), Object.values() и Object.entries().

                              –5
                              Может проще выбрать простой язык, который компилируется в javascript?
                                0
                                Например, какой?
                                  0
                                  Самый простой — Elm, он покроет 90% потребностей фронтенда. Посложнее и помощнее PureScript, его и с node.js хорошо использовать. Есть много других, но я их особо не смотрел.
                                    +1
                                    Я большой любитель функциональных языков, но в целом мне не кажется, что их можно назвать «простыми» для основной массы разработчиков. Если за Elm мне говорить сложно, то PureScript, на мой взгляд, будет простым только для хаскеллщиков.
                                      0
                                      В целом, на мой взгляд, в массе своей языки, компилируемые в JS, обычно значительно сложнее самого JS — в том плане, что порог вхождения ощутимо выше.
                                      И это довольно логично, поскольку создаются они с целью быть мощнее и выразительнее JS — теми, кому не хватило его возможностей.
                                        0
                                        Синтаксис проще (хотя отсутствие лишних скобок и запятых некоторых пугает). Семантика проще, так как вся работа с изменяемым состоянием изолирована. А с монадами джаваскриптерам все равно приходится разбираться для ассинхронных операций, даже если они такого слова не призносят.
                                        Ошибки типизации разбирать не сложнее, чем ошибки runtime. Тем более, что многие сейчас импользуют typescript.
                                        Elm еще избавляет от работы с DOM, которая привносит много сложности.
                                        В общем страх перед функциональщиной совершенно не обоснован.
                                          0
                                          В общем страх перед функциональщиной совершенно не обоснован.
                                          Совершенно согласен.
                                          Но это не отменяет того, что очень многим (включая и меня) разработчикам, выросшим (профессионально) на императивном программировании, функциональное даётся отнюдь не «просто».

                                          многие сейчас импользуют typescript
                                          И TypeScript объективно сложнее JS.
                                          Как в плане планки входа в язык (нужно знать JS + систему типов TS + как одно преобразуется в другое), так и в плане оверхеда при разработке (что на маленьких проектах часто делает TS избыточным).
                                            0
                                            И TypeScript объективно сложнее JS.

                                            Потому что привносит сложную систему типов в уже существующий сложный язык, не рассчитанный на статическую типизацию.
                                            Тем ни мение разработчики научились с этими типами жить.
                                            А системы типов большенства функциональных языков гораздо проще.
                                  +1
                                  Если человек, читающий статью, мало знаком или не знаком с большей частью написанного, то генераторы ему явно ни к чему. И впрямь, статья — где понадёргано всё подряд без разбора. Ценности не представляет совершенно, но за труды по переводу всё равно, как водится, спасибо.
                                    –2

                                    Я наконец то нашёл адекватно и простое для моего понимания описание замыканий. Яба-даба-ду!!! XD XD XD.
                                    Не вот серьёзно — куда бы не посмотрел какая-то муть. Т.е. описание что это такое — есть, а вот ЗАЧЕМ ОНО — в большинстве случаев опускается.

                                      +2
                                      В разделе 3 «Деструктурирующее присваивание» написано:
                                      В следующем примере деструктурирование используется для аккуратной передачи значений, хранящихся в свойствах объекта person, функции introduce().

                                      Что означает «для аккуратной передачи значений»? Есть еще и не аккуратная передача значений?
                                        0
                                        Вот, например, очень неаккуратная передача значений (тоже через деструктурирование, правда). :D

                                        let [a, b, c] = shuffle([1, 2, 3]);
                                        
                                        function shuffle(a) {
                                            var j, x, i;
                                            for (i = a.length - 1; i > 0; i--) {
                                                j = Math.floor(Math.random() * (i + 1));
                                                x = a[i];
                                                a[i] = a[j];
                                                a[j] = x;
                                            }
                                            return a;
                                        }


                                        Вообще в оригинале «destructuring is used to cleanly pass the person object to the introduce function» — т. е. скорее «для опрятной» (в плане самого кода).
                                          0
                                          да, когда целиком объект принимается без деструктурирования, логично же
                                          0
                                          По сравнению обьектов как-то мало инфы. Написано преобразовании в формат JSON-строк. А условно использование функций как toString или даже valueOf с вычислением хеша.
                                            +1
                                            Какие ещё концепции JavaScript вы добавили бы в эту статью?

                                            сall()/bind()/apply(), примеры MDN.

                                            call:
                                            function Product(name, price) {
                                              this.name = name;
                                              this.price = price;
                                            
                                              if (price < 0) {
                                                throw RangeError('Нельзя создать продукт ' +
                                                                  this.name + ' с отрицательной ценой');
                                              }
                                            
                                              return this;
                                            }
                                            
                                            function Food(name, price) {
                                              Product.call(this, name, price);
                                              this.category = 'еда';
                                            }
                                            
                                            Food.prototype = Object.create(Product.prototype);
                                            
                                            function Toy(name, price) {
                                              Product.call(this, name, price);
                                              this.category = 'игрушка';
                                            }
                                            
                                            Toy.prototype = Object.create(Product.prototype);
                                            
                                            var cheese = new Food('фета', 5);
                                            var fun = new Toy('робот', 40);
                                            

                                            bind:
                                            this.x = 9;
                                            var module = {
                                              x: 81,
                                              getX: function() { return this.x; }
                                            };
                                            
                                            module.getX(); // 81
                                            
                                            var getX = module.getX;
                                            getX(); // 9, поскольку в этом случае this ссылается на глобальный объект
                                            
                                            // создаём новую функцию с this, привязанным к module
                                            var boundGetX = getX.bind(module);
                                            boundGetX(); // 81
                                            

                                            apply:
                                            /* мин/макс числа в массиве */
                                            var numbers = [5, 6, 2, 3, 7];
                                            
                                            /* используем apply к Math.min/Math.max */
                                            var max = Math.max.apply(null, numbers); /* Это эквивалентно Math.max(numbers[0], ...)
                                                                                        или Math.max(5, 6, ...) */
                                            var min = Math.min.apply(null, numbers);
                                            
                                            /* сравним с простым алгоритмом с циклом */
                                            max = -Infinity, min = +Infinity;
                                            
                                            for (var i = 0; i < numbers.length; i++) {
                                              if (numbers[i] > max) {
                                                max = numbers[i];
                                              }
                                              if (numbers[i] < min) {
                                                min = numbers[i];
                                              }
                                            }
                                            
                                              –1
                                              Поправьте в примере по промисам ошибку.
                                              Вы делаете reject, но не делаете обработку этого реджекта.
                                              catch !== reject
                                                0

                                                А это тогда что?


                                                   .catch(function(err) {
                                                    console.log('Error: ' + err);
                                                   });
                                                  –1
                                                  Catch сработает когда в теле промиса будет ошибка.
                                                  Например, throw new Error.

                                                  Важно понимать, что catch !== reject
                                                    0

                                                    И чем же, по-вашему, throw new Error отличается от reject(new Error)?

                                                      0
                                                      throw new Error выбросит вас в catch,
                                                      reject (new Error) должен вам вернуть экземляр ошибки в реджекте (второй аргумент у промиса)
                                                        0

                                                        А давайте вы уже почитаете стандарт? Там довольно прозрачно написано, что вызовы .catch(foo) и .then(undefined, foo) полностью эквивалентны.


                                                        Или под "вторым аргументом у промиса" вы имели в виду что-то другое?

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое