Оригинальная статья: Casper Beyer — Lesser-Known JavaScript Hazards
С тех пор, как мы преодолели ECMAScript 4 Harmony, в JavaScript появилось много новых, интересных функций. В то время как больше возможностей может позволить нам создать более качественный и читаемый код, также легко упустить из виду источники потенциальных проблем. Давайте рассмотрим некоторые часто встречаемые ошибки, причина которых не всегда очевидна.
Стрелочные функции обеспечивают более короткий синтаксис, одна из доступных функций заключается в том, что вы можете написать свою функцию в виде лямбда-выражения с неявным возвращаемым значением. Это удобно при использование функционального стиля, например, когда вы должны использовать функцию map c массивом.
Для примера:
const numbers = [1, 2, 3, 4]; numbers.map(function(n) { return n * n; });
Этот код можно упростить с использованием стрелочных функций и сделать из него однострочник:
const numbers = [1, 2, 3, 4]; numbers.map(n => n * n);
Этот вариант использования со стрелочной функцией будет работать так, как можно было бы ожидать: функция переумножает значения и возвращает новый массив, содержащий [1, 4, 9, 16].
Однако, если мы попытаетесь мапировать объекты (а не простое выражение), синтаксис должен не таким, как можно было бы интуитивно ожидать от него. Например, допустим, мы пытаемся мапировать наши числа в массив объектов, содержащих значение, подобное этому:
const numbers = [1, 2, 3, 4]; numbers.map(n => { value: n }); // [undefined, undefined, undefined, undefined]
Результатом здесь будет массив, содержащий неопределенные значения. Хотя мы может ожидать, что здесь будет возвращаться объект, интерпретатор видит что-то совершенно другое. Фигурные скобки интерпретируются как область видимости стрелочной функции, и переменная value фактически становится меткой Если бы мы экстраполировали вышеприведенную стрелочную функцию на то, что фактически выполняет интерпретатор, это выглядело бы примерно так:
const numbers = [1, 2, 3, 4]; numbers.map(function(n) { value: n return; });
Обходной путь довольно хитрый. Нам просто нужно обернуть объект в круглые скобки, что превращает его в выражение вместо выражения блока, например так:
const numbers = [1, 2, 3, 4]; numbers.map(n => ({ value: n }));
Это приведет к массиву, содержащему массив объектов со значениями, которые можно ожидать.
Еще одна хитрость, связанная с функциями стрелок, заключается в том, что у них нет своих привязок к this, то есть их значение this будет таким же, как и значение this лексической области видимости.
Таким образом, несмотря на то, что синтаксис может быть более простым, функции стрелок не являются заменой старых добрых функций. Вы можете быстро столкнуться с ситуацией, когда ваша this будет не соответствует вашим ожиданиям.
For example:
let calculator = { value: 0, add: (values) => { this.value = values.reduce((a, v) => a + v, this.value); }, }; calculator.add([1, 2, 3]); console.log(calculator.value);
Хотя можно ожидать, что this здесь будет объектом калькулятора, на самом деле это приведет к тому, что this будет либо неопределенной, либо глобальным объектом в зависимости от того, выполняется код в строгом режиме или нет. Это потому, что ближайшая лексическая область действия здесь — это глобальная область действия. В строгом режиме strict mode это undefined; в противном случае это объект window в браузерах (или объект process в среде, совместимой с Node.js).
В случае использования обычных функций когда вызывается объект, this будет указывать на объект, поэтому использование обычной функции все еще является способом использования функций-членов.
let calculator = { value: 0, add(values) { this.value = values.reduce((a, v) => a + v, this.value); }, }; calculator.add([10, 10]); console.log(calculator.value);
Кроме того, поскольку стрелочная функция не имеет этой привязки, Function.prototype.call, Function.prototype.bind и Function.prototype.apply также не будут работать с ними.
Итак, в следующем примере мы столкнемся с той же проблемой, что и раньше: this является глобальным объектом, когда вызывается функция добавления сумматора, несмотря на нашу попытку переопределить ее с помощью Function.prototype.call:
const adder = { add: (values) => { this.value = values.reduce((a, v) => a + v, this.value); }, }; let calculator = { value: 0 }; adder.add.call(calculator, [1, 2, 3]);
Хотя стрелочные функции более аккуратны, но они не могут заменить обычные функции-члены, где требуется this.
Хотя это не новая функция, автоматическая вставка точек с запятой (ASI) является одной из более странных функций в JavaScript, поэтому ее стоит упомянуть. Теоретически, вы можете пропускать точку с запятой в большей части кода (что происходит во многих проектах). Если у вас так принято то в этом нет ничего плохого, но вы должны знать что может произойти.
Возьмите следующий пример:
return
{
value: 42
}
Можно подумать, что он вернет литерал объекта, но на самом деле он вернет неопределенное значение, потому что происходит автоматическая вставка точки с запятой, что делает его пустым оператором возврата, за которым следуют оператор блока.
Другими словами, окончательный код, который фактически интерпретируется, больше похож на следующий:
return;
{
value: 42
};
Поэтому лучше, никогда не начинайте строку с открывающей скобки или строкового литерала, даже при использовании точек с запятой, потому что ASI всегда присутствует.
Множества это по сути массив с не повторяющими элементами. Но
множества являются поверхностными (Shallow), что означает при наличие одинаковых массивов или объектов, они будут рассматриваться как уникальные элементы.
Для примера:
let set = new Set(); set.add([1, 2, 3]); set.add([1, 2, 3]); console.log(set.size); // 2
Размер этого множества будет равен двум, что имеет смысл, если рассматривать его элементы с точки зрения ссылок, и поэтому они являются разными объектами.
Однако строки неизменяемы (immutable). И одинаковые строки не будут дублироваться:
<strong>let</strong> set = <strong>new</strong> Set(); set.add([1, 2, 3].join(',')); set.add([1, 2, 3].join(',')); console.log(set.size); // 1
В результате размер множества будет равен единице, поскольку строки неизменяемы и встроены в JavaScript, что можно использовать в качестве обходного пути, если вам необходимо сохранить набор объектов. Их можно сериализовать и десериализовать.
В JavaScript обычные функции поднимаются в верхнюю часть лексической области (hoisting), что означает, что приведенный ниже пример будет работать так, как можно было ожидать:
let segment = new Segment(); function Segment() { this.x = 0; this.y = 0; }
Но то же самое не относится к классам. Классы не поднимаются и должны быть полностью определены в лексической области видимости, прежде чем пытаться их использовать.
Например:
let segment = new Segment(); class Segment { constructor() { this.x = 0; this.y = 0; } }
Этот код приведет к возникновению ошибки ReferenceError при попытке создать новый экземпляр класса, потому что они не отображаются как функции.
Finally это особый случай. Взгляните на следующий фрагмент:
try {
return true;
} finally {
return false;
}
Как вы думаете, что тут возвращается? Ответ является интуитивным понятным и в то же время может стать не совсем очевидным. Можно подумать, что первый оператор return заставляет функцию действительно завершиться и вернуть стек вызовов. Но в данном случае это исключение из этого правила, потому что оператор finally так же должен всегда выполнится, поэтому вместо return true возвращается оператор return false внутри блока finally.
JavaScript легко выучить, но трудно освоить. Другими словами, он подвержен ошибкам и не очевидному поведению, если разработчик не заботится о том, что и почему он что-то делает.
Это особенно верно для ECMAScript 6 и его новых функций. В частности, особенности поведения есть в стрелочных функциях по. Если бы я сделал предположение, я бы сказал, что это потому, что разработчики рассматривают их как синтаксическая замена обычных функций. Но они не являются обычными функциями и не могут их заменить.
Чтение спецификации время от времени не повредит. Это не самое захватывающее чтиво в мире, но с точки зрения других спецификаций он не так уж и плох.
Такие инструменты, как AST Explorer, также помогают пролить свет на то, что происходит в некоторых из этих сложных случаев. Люди и компьютеры склонны видеть вещи по-разному.
С учетом сказанного я оставлю вас с последним примером в качестве упражнения.
Вопросы по JavaScript
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…