Pull to refresh

Comments 61

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

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

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


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


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

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

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

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

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

[].push(10); // => 1
Вопрос слегка не по теме (но напомнило описанием методов 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] 


Единственный раз, когда использовал reduceRight(), был на codewars. Кажется там именно «right» позволял написать код лаконичнее. Но подробностей, увы, не помню.
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) в ситуации, когда нужно копировать большое количество мелких блоков.
Спасибо, хорошее объяснение необходимости copyWithin() и наглядный пример.
Его, в целом можно обобщить — copyWithin() позволяет реализовать быструю фильтрацию массива in-place. :)
Выбрать в массиве все элементы, удовлетворяющие некоторому условию, переместить их в начало массива и вернуть новую эффективную длину массива — это не фильтрация?
Ну да, абсолютно точно не в том смысле, как функциональный filter(). Поэтому я использовал (придуманный на ходу) термин «фильтрация in-place», как мне кажется, он неплохо передаёт суть. Есть более общепринятый термин для такого алгоритма?
Выбрать в массиве все элементы, удовлетворяющие некоторому условию

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

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

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

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

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

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

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

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

Вероятно, это просто дело вкуса.
Это когда оператор коммутативен (value + res === res + value). А если нет? Мало ли какие могут быть варианты.
Эм, так конкатенация строк как раз-таки не коммутативна.
{
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)
}
Тут надо иметь в виду, что вот как раз reverse переворачивает массив «на месте», то есть способ с его использованием может оказаться нежелателен, если мы хотим, чтобы в переменной array порядок сохранился.
Ага, то есть тут reduceRight() прямо максимально к месту.

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

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

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

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

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

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

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

Что означает «для аккуратной передачи значений»? Есть еще и не аккуратная передача значений?
Вот, например, очень неаккуратная передача значений (тоже через деструктурирование, правда). :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» — т. е. скорее «для опрятной» (в плане самого кода).
да, когда целиком объект принимается без деструктурирования, логично же
По сравнению обьектов как-то мало инфы. Написано преобразовании в формат JSON-строк. А условно использование функций как toString или даже valueOf с вычислением хеша.
Какие ещё концепции 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];
  }
}
Поправьте в примере по промисам ошибку.
Вы делаете reject, но не делаете обработку этого реджекта.
catch !== reject

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


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

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

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

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

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


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

Sign up to leave a comment.