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 вы добавили бы в эту статью?

Комментарии (53)


  1. CoolCmd
    25.02.2019 12:51
    +1

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


    1. GCU
      25.02.2019 18:54
      +5

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


    1. Zoolander
      26.02.2019 08:52

      по крайней мере такие статьи полезны в плане «повторение — мать учения»
      хотя для опытных разработчиков они могут показаться утомительными и разочаровывающими


    1. namikiri
      26.02.2019 11:56

      4. Впихиваем рекламу своего продукта. Ведь ради этого написана статья, не так ли?


  1. mithron
    25.02.2019 12:55

    Что стоит добавить — это в каких стандартах языка всё это работает. Статья явно рассчитана на не очень опытного разработчика, и может запутать в этом смысле.


    1. AlexTheLost
      25.02.2019 13:19

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


      1. shaukote
        25.02.2019 15:13

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


  1. inoyakaigor
    25.02.2019 14:14

    Оператор rest

    Это не оператор. Это синтаксис. MDN


  1. shaukote
    25.02.2019 15:10

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

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

    [].push(10); // => 1


  1. shaukote
    25.02.2019 15:29

    Вопрос слегка не по теме (но напомнило описанием методов 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] 



    1. Tenebrius
      25.02.2019 15:50

      Единственный раз, когда использовал reduceRight(), был на codewars. Кажется там именно «right» позволял написать код лаконичнее. Но подробностей, увы, не помню.


    1. CoolCmd
      25.02.2019 17:18

      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) в ситуации, когда нужно копировать большое количество мелких блоков.


      1. shaukote
        26.02.2019 11:15

        Спасибо, хорошее объяснение необходимости copyWithin() и наглядный пример.
        Его, в целом можно обобщить — copyWithin() позволяет реализовать быструю фильтрацию массива in-place. :)


        1. CoolCmd
          26.02.2019 12:04

          это не фильтрация


          1. shaukote
            26.02.2019 12:38

            Выбрать в массиве все элементы, удовлетворяющие некоторому условию, переместить их в начало массива и вернуть новую эффективную длину массива — это не фильтрация?
            Ну да, абсолютно точно не в том смысле, как функциональный filter(). Поэтому я использовал (придуманный на ходу) термин «фильтрация in-place», как мне кажется, он неплохо передаёт суть. Есть более общепринятый термин для такого алгоритма?


            1. CoolCmd
              26.02.2019 15:42

              Выбрать в массиве все элементы, удовлетворяющие некоторому условию

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


              1. shaukote
                26.02.2019 16:13

                Вы, видимо, не очень внимательно прочитали мой комментарий.

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

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


    1. torbasow
      26.02.2019 14:08

      reduceRight полезен, если нам нужно пройти по массиву, и при этом некоторые его элементы могут быть в этом процессе выкинуты.


      1. shaukote
        26.02.2019 17:37

        А можете чуть более конкретный пример привести? Я пока не очень понимаю, о чём речь. :(


        1. torbasow
          26.02.2019 21:37

          Ну, вот у меня в одном случае был массив данных, каждый элемент в котором представлял собой объект, который мог нести пометку, что в некоторый момент он должен быть удалён. И вот наступает этот момент. Решением «в лоб» было использование метода массивов filter, но он не фильтрует массив «на месте», он его пересоздаёт, следовательно на свойстве, которому он присвоен, сработает сеттер, а проверки на равенство с другими свойствами, где ссылка на него была сохранена ранее, дадут false.
          И вот тут оказался как нельзя кстати reduceRight. Конечно, есть и другие варианты, но этот, возможно, самый лаконичный. Перебор, устойчивый к выкидыванию элементов в процессе.


          1. shaukote
            27.02.2019 10:48

            Перебор, устойчивый к выкидыванию элементов в процессе.
            Ох, возможно, он и лаконичный, но мне лично пришлось немного поломать голову. Как-то я никогда не воспринимал так 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);


            1. mayorovp
              27.02.2019 13:56

              Похоже на ещё одну вариацию алгоритма маляра Шлемиэля…


              1. shaukote
                27.02.2019 13:59

                Да, я тоже об этом подумал, пока писал.
                По-хорошему надо удалять непрерывными последовательностями — например через copyWithin(), чтобы было комбо. :)
                Но мне важнее было получить простейший и наглядный пример.


                1. ecmaeology
                  27.02.2019 20:00

                  let n = array.length
                  while (n--) {
                    if (array[n].removed) {
                      const swap = array.pop()
                      if (array.length != n)
                        array[n] = swap
                    }
                  }

                  Так будет эффективнее, хотя порядок элементов изменится.


                  1. shaukote
                    27.02.2019 20:59

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

                    В целом, ничто не мешает аналогичный код написать внутри reduceRight() — если в процессе обработки нужно свернуть массив. Но это уже дело вкуса.


                    1. mayorovp
                      28.02.2019 08:51

                      По-хорошему, внутрь reduceRight нужно передавать чистую функцию.


                      1. shaukote
                        28.02.2019 09:54

                        Спорное утверждение в языке с отсутствием иммутабельности. Вообще, изначально идея удалять элементы в reduceRight() была torbasow, вопросы к нему. :D

                        Но мне субъективно проще воспринимать алгоритм через reduce (пусть его коллбэк и не чистая функция), чем через while.


    1. stranger777
      26.02.2019 16:21

      Пример reduceRight() — работа с порядком элементов в списках, т.е. банально иногда удобнее справа налево (оно же с последнего до первого.)


      1. shaukote
        26.02.2019 17:35

        Да, это самый популярный пример.

        На мой взгляд, он, наоборот, сложнее для понимания, и вариант через reduce() гораздо читабельнее (именование аргументов ещё решает):

        ['1', '2', '3', '4', '5'].reduce((res, value) => value + res); 
        

        Вероятно, это просто дело вкуса.


        1. torbasow
          26.02.2019 21:42

          Это когда оператор коммутативен (value + res === res + value). А если нет? Мало ли какие могут быть варианты.


          1. shaukote
            26.02.2019 22:40

            Эм, так конкатенация строк как раз-таки не коммутативна.


    1. ecmaeology
      26.02.2019 19:40

      {
      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)
      }
      


      1. shaukote
        26.02.2019 20:00

        Ух ты, какой отличный пример! Спасибо!


        1. torbasow
          26.02.2019 21:48

          Тут надо иметь в виду, что вот как раз reverse переворачивает массив «на месте», то есть способ с его использованием может оказаться нежелателен, если мы хотим, чтобы в переменной array порядок сохранился.


          1. shaukote
            26.02.2019 22:41

            Ага, то есть тут reduceRight() прямо максимально к месту.

            Плюс мне очень понравился сам способ построения списка из массива — лаконично и выразительно, прямо очень sexy. :)


  1. XAHTEP26
    25.02.2019 16:37

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

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


    1. ru_vds Автор
      25.02.2019 16:42
      +1

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


  1. irsick
    25.02.2019 16:46

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


  1. potan
    25.02.2019 18:56
    -2

    Может проще выбрать простой язык, который компилируется в javascript?


    1. shaukote
      26.02.2019 10:45

      Например, какой?


      1. potan
        26.02.2019 12:06

        Самый простой — Elm, он покроет 90% потребностей фронтенда. Посложнее и помощнее PureScript, его и с node.js хорошо использовать. Есть много других, но я их особо не смотрел.


        1. shaukote
          26.02.2019 12:32

          Я большой любитель функциональных языков, но в целом мне не кажется, что их можно назвать «простыми» для основной массы разработчиков. Если за Elm мне говорить сложно, то PureScript, на мой взгляд, будет простым только для хаскеллщиков.


        1. shaukote
          26.02.2019 12:35

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


          1. potan
            26.02.2019 14:22

            Синтаксис проще (хотя отсутствие лишних скобок и запятых некоторых пугает). Семантика проще, так как вся работа с изменяемым состоянием изолирована. А с монадами джаваскриптерам все равно приходится разбираться для ассинхронных операций, даже если они такого слова не призносят.
            Ошибки типизации разбирать не сложнее, чем ошибки runtime. Тем более, что многие сейчас импользуют typescript.
            Elm еще избавляет от работы с DOM, которая привносит много сложности.
            В общем страх перед функциональщиной совершенно не обоснован.


            1. shaukote
              26.02.2019 14:59

              В общем страх перед функциональщиной совершенно не обоснован.
              Совершенно согласен.
              Но это не отменяет того, что очень многим (включая и меня) разработчикам, выросшим (профессионально) на императивном программировании, функциональное даётся отнюдь не «просто».

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


              1. potan
                26.02.2019 15:52

                И TypeScript объективно сложнее JS.

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


  1. Arfeo
    25.02.2019 20:06

    Если человек, читающий статью, мало знаком или не знаком с большей частью написанного, то генераторы ему явно ни к чему. И впрямь, статья — где понадёргано всё подряд без разбора. Ценности не представляет совершенно, но за труды по переводу всё равно, как водится, спасибо.


  1. White_Scorpion
    26.02.2019 00:36

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


  1. Ti_Fix
    26.02.2019 10:51

    В разделе 3 «Деструктурирующее присваивание» написано:

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

    Что означает «для аккуратной передачи значений»? Есть еще и не аккуратная передача значений?


    1. shaukote
      26.02.2019 11:27

      Вот, например, очень неаккуратная передача значений (тоже через деструктурирование, правда). :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» — т. е. скорее «для опрятной» (в плане самого кода).


    1. rework
      26.02.2019 11:40

      да, когда целиком объект принимается без деструктурирования, логично же


  1. vshyrokov
    26.02.2019 11:58

    По сравнению обьектов как-то мало инфы. Написано преобразовании в формат JSON-строк. А условно использование функций как toString или даже valueOf с вычислением хеша.


  1. stranger777
    27.02.2019 18:49

    Какие ещё концепции 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];
      }
    }