Функция литерал которая имеет ровно один аргумент
Перейти к содержимому

Функция литерал которая имеет ровно один аргумент

  • автор:

Лекция 9. Литералы объектов, JSON, функции обратного вызова

Этот чистый новый объект не представляет интереса, до тех пор, пока мы не начнем добавлять к нему некоторые свойства.

Объекты JS могут содержать данные и обладать методами. Но в отличие от других ЯП эти элементы в объекте заранее не объявлены, мы создаём их динамически, по мере необходимости.

var ride = new Object(); ride.make = ‘Yamaha’; ride.model = ‘V-Star Silverado’ ride.year = 2005; ride.purchased = new Date(2005,3,15);

Мы создаём новый экземпляр Object и присваиваем его переменной ride. Затем заполняем эту переменную рядом свойств различных типов: два строковых свойства, числовое свойство и свойство типа Date.

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

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

ride.purchased = new Date(2005,2,1);

Ничего страшного… если мы случайно не сделаем опечатку, например:

ride.purcahsed = new Date(2005,2,1);

Как вы знаете, в JS нет компилятора, который бы предупредил бы вас о сделанной ошибке; новое свойство с именем purcahsed, позже вызовет ряд проблем в работе вашей программы. Частично в решении таких проблем вам может помочь Firebug.

Из данного примера видно, что экземпляр Object, или просто объект – набор свойств, каждое из которых состоит из имени и значения. Имя свойства – это строка, а значение может быть любым объектом JS.

Т.о. основная цель экземпляра Object – служить контейнером для именованных наборов других объектов.

Предположим, мы добавили к нашему объекту ride новое свойство, описывающее владельца транспортного средства. Это свойство – еще один объект JS, который в свою очередь содержит такие свойства, как имя и профессия владельца:

var owner = new Object(); owner.name = ‘Konstantin Volodin’; owner.occupation = ‘bounty hunter’; ride.owner = owner;

Обращение к вложенным свойствам можно описать так:

var ownerName = ride.owner.name;

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

Понятно, что создавать промежуточные переменные (такие как owner) излишне – мы создавали их в этих фрагментах исключительно для наглядности. Далее мы увидим более эффективные и компактные способы объявления объектов и их свойств.

До этого момента мы ссылались на свойства объекта с помощью опрератора «точка», но он является синонимом более общего оператора для выполнения ссылок на свойства.

Что делать, если есть свойство с именем color.scheme? Точка в середине имени портит всё дело. Интерпретатор JS будет пытаться найти scheme как вложенное свойство color. Такая же проблема будет с другими символами, пробельными и специальными. Для таких случаев используется более общий формат обращения к свойствам:

object[propertyNameExpression]

propertyNameExpression – выражение JS, которое определяется как строка, формирующая имя свойства, к которому происходит обращение. Например, все три следующие ссылки эквивалентны:

ride.make ride[‘make’] ride[‘m’+’a’+’k’+’e’]
var p = ‘make’; ride[p];

Применение общего оператора ссылки – единственный способ обратиться к свойствам, имена которых не являются допустимыми идентификаторами JS, например:

ride[“a property name that’s rather odd”]

Такие имена содержат символы, недопустимые для идентификаторов JS, или являются значениями других переменных.

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

Литералы объектов.

Литералами называют представление значения некоторого типа данных. В следующем примере 2 и Пример это литералы, а a2, d и example — переменные:

int a2 = 2; int d = a2; string example = "Пример";

При создании объекта ride , моделирующего некоторые свойства мотоцикла, мы использовали два оператора new, промежуточную переменную с именем owner и много операторов присваивания. Это утомительно.

Можно использовать более компактную и удобную для визуального восприятия запись

var ride = < make: ‘Yamaha’, model: ‘V-Star Silverado’, year: 2005, purchased: new Date(2005,3,15), owner: < name: ‘Konstantin Volodin’, occupation: ‘bounty hunter’, >>;

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

Большинство авторов страниц отдают предпочтение такой форме записи, называемой JSON (JavaScript Object Notation). Структура этой формы записи очень проста – объект обозначается парой фигурных скобок, внутри которых через запятую перечислены свойства. Каждое свойство обозначается путём записи его имени и значения, разделённых символом двоеточия.

Как видно из объявления свойства owner, объявления объектов могут быть вложенными.

Кстати, точно также в формате JSON можно описывать массивы, поместив список элементов, разделённых запятыми, в квадратные скобки:

var someValues = [2,4,5,8,9,12,14,16,17,23,36];

JSON (JavaScript Object Notation) — простой формат обмена данными, удобный для чтения и написания как человеком, так и компьютером. JSON — текстовый формат, полностью независимый от языка реализации, но он использует соглашения, знакомые программистам C-подобных языков, таких как C, C++, C#, Java, JavaScript, Perl, Python и многих других. Эти свойства делают JSON идеальным языком обмена данными.

  • Коллекция пар ключ/значение. В разных языках, эта концепция реализована как объект, запись, структура, словарь, хэш, именованный список или ассоциативный массив.
  • Упорядоченный список значений. В большинстве языков это реализовано как массив, вектор, список или последовательность.

Это универсальные структуры данных. Почти все современные языки программирования поддерживают их в какой-либо форме. Логично предположить, что формат данных, независимый от языка программирования, должен быть основан на этих структурах.

В нотации JSON это выглядит так:

Объект — неупорядоченный набор пар ключ/значение. Объект начинается с < (открывающей фигурной скобки) и заканчивается >(закрывающей фигурной скобкой). Каждое имя сопровождается : (двоеточием), пары ключ/значение разделяются , (запятой).

Массив — упорядоченная коллекция значений. Массив начинается с [ (открывающей квадратной скобки) и заканчивается ] (закрывающей квадратной скобкой). Значения разделены , (запятой).

Значение может быть строкой в двойных кавычках, числом, true , false , null , объектом или массивом. Эти структуры могут быть вложенными.

Строка — коллекция нуля или больше символов Unicode, заключенная в двойные кавычки, используя \ (обратную косую черту) в качестве символа экранирования. Символ представляется как односимвольная строка. Похожий синтаксис используется в C и Java.

Число представляется так же, как в C или Java, кроме того, что используется только десятичная система счисления.

Пробелы могут использоваться между любыми лексемами.

Вернёмся к нашему объекту ride.

Как было показано в примерах выше, ссылки на объекты часто хранятся в переменных или в свойствах других объектов. Рассмотрим особый случай последнего сценария.

Объекты как свойства объекта window

До этого момента мы видели два способа хранения ссылок на объекты JS – в переменных и в свойствах.

var aVariable = ‘No one can draw a clear line between sane and insane.’; someObject.aProperty = ‘You move that line as you see fit for yourself.’;

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

Однако, действительно ли эти инструкции выполняют разные операции? Как выясняется – нет!

Когда ключевое слово var используется на глобальном уровне, за пределами какой-либо функции, эта удобная для программиста форма записи является всего лишь ссылкой на свойство предопределённого объекта JS window. Любая глобальная ссылка неявно превращается в свойство объекта window.

Это означает, что эквивалентны все следующие инструкции:

var foo = bar; window.foo = bar; foo = bar;

Независимо от формы записи во всех трёх случаях создается свойство объекта window с именем foo (если оно не существовало), и ему присваивается значение bar. Кроме того, поскольку идентификатор bar никак не квалифицирован, предполагается, что он представляет собой имя свойства объекта window.

Однако это не означает, что глобальная область видимости – это область видимости объекта window, потому что любые неквалифицированные глобальные ссылки подразумевают ссылки на свойства объекта window. Правила области видимости усложнятся, когда мы углубимся в изучение тел функций.

В целом, обзор объекта Object в языке JavaScript завершен. Были рассмотрены следующие важные понятия:

  • Объект в JS – это неупорядоченный набор свойств.
  • Свойство состоит из имени и значения.
  • Можно объявлять объекты посредством литералов объектов.
  • Глобальные переменные являются свойствами объекта window.

Функции как обычные объекты

В языке JS функции считаются объектами, подобно объектам любого другого типа, например string, number или Date. Как и другие объекты, функции определяются конструктором JS, в данном случае – Function, и могут:

  • присваиваться переменным;
  • присваиваться свойствам объектов;
  • передаваться в виде параметров;
  • возвращаться как результат других функций;
  • создаваться с помощью литералов.

Поскольку в некоторых случаях функции рассматриваются как объекты, мы говорим, что функции – это обычные объекты. И несмотря на то, что они обладают не только значением (тело функции), но ещё и именем, функции не отличаются от других объектов JS.

Что есть имя?

Функции в JS не являются именованными сущностями. Как и с другими типами объектов, на функции ссылаются, только когда они присваиваются переменным, свойствам или параметрам.

Рассмотрим объект типа Number. Инструкция

Вполне правильна, но совершенно бесполезна. Экземпляр Number не всегда полезен, если не присваивается свойству или переменной или не связан с именем параметра. Мы просто не сможем обратиться к такому экземпляру. Это же относится и к экземплярам объектов Function.

function doSmthWonderful()

Не создаёт функцию с именем doSmthWonderful. Ключевое слово function автоматически создаёт экземпляр Function и присваивает его свойству window, имя которого совпадает с «именем» функции, как показано в следующем примере:

doSmthWonderful = function()

Это аналогично объявлению переменной с литералом Number

aWonderfulNumber = 213;

Здесь нет ничего странного, и инструкция присваивания функции глобальной переменной (свойству объекта window) ничем не отличается – литерал функции используется для создания экземпляра Function, а затем присваивается переменной doSmthWonderful так же, как литерал 213 объекта Number был использован для присваивания экземпляра Number переменной aWonderfulNumber.

Литерал функции состоит из ключевого слова function, за которым идёт список параметров, заключённый в круглые скобки, и далее следует тело функции.

Когда мы объявляем глобальную именованную функцию, создаётся экземпляр Function и присваивается свойству объекта window, которое создаётся автоматически, на основе так называемого имени функции. Сам по себе экземпляр Function не имеет имени, как литерал Number или String. Это понятие иллюстрирует рисунок:

Рисунок 5. Экземпляр Function является неименованным объектом, таким же, как значение 213 типа Number или любое другое значение JavaScript.
Он именуется только ссылками, которые созданы для него.

Помните, что, когда в HTML-странице создаётся глобальная переменная, она создаётся как свойство объекта window. Поэтому все следующие инструкции эквивалентны:

function hello() hello = function() < alert(‘Hi there!’); >window.hello = function()

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

Мы рассмотрели примеры присваивания функций переменным и свойствам – а как насчет передачи функции в качестве параметра?

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

Глобальные функции хороши, когда программный код следует красивым и упорядоченным синхронным потоком, но характерной чертой HTML-страниц сразу после загрузки является асинхронность. Будь то обработка событий, установка таймеров или выполнение запросов Ajax, программный код веб-страницы по своей природе является асинхронным. И одно из самых распространённых понятий в асинхронном программировании – понятие функции обратного вызова.

Возьмём в качестве примера таймер. Мы можем заставить таймер сработать, например, через 5 секунд, передав соответствующее значение длительности методу window.setTimeout(). Но каким образом этот метод сообщить нам о том, что время таймера истекло, чтобы мы могли выполнить необходимые действия по истечении времени ожидания? Делается это путём вызова функции, котору мы предоставим.

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

function hello() setTimeout(hello,5000);

Мы объявляем функцию с именем hello и устанавливаем таймер на 5 секунд, заданных во втором параметре как 5000 миллисекунд. В первом параметре мы передаём методу SetTimeout() ссылку на функцию. Передача функции в виде параметра ничем не отличается от передачи любого другого значения – точно также мы передали во втором параметре значение Number.

Когда время таймера истечёт, будет вызвана функция hello. Поскольку метод setTimeout() делает обратный вызов функции в нашем собственном программном коде, эту функцию называют функцией обратного вызова.

Однако, создание имени hello не является необходимостью. Если функцию не требуется вызывать где-нибудь в другом месте страницы, нет никакой необходимости создавать свойство hello в объекте window, чтобы на мгновение сохранить экземпляр Function и потом передать его в качестве параметра.

Вот более изящный способ записи этого фрагмента:

setTimeout(function() < alert(‘Hi there!’); >,5000);

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

Мы также можем присваивать экземпляры Function свойствам объектов, и эта возможность действительно представляет интерес.

В объектно-ориентированных языках, основанных на классах, указатель this, как правило, ссылается на экземпляр класса, в пределах которого был объявлен метод. В JS, где функции являются обычными объектами, они не объявляются как часть чего либо. Объект, на который ссылается this, называется контекстом функции и определяется не тем, как функция объявляется, а тем, как она вызывается.

Это означает, что одна и таже функция может иметь различный контекст в зависимости от того, как она вызывается. По умолчанию контекст (this) функции – это объект, свойство которого содержит ссылку для вызова функции. Вернемся к нашему примеру с мотоциклом и изменим создание объекта:

var ride = < make: ‘Yamaha’, model: ‘V-Star Silverado’, year: 2005, purchased: new Date(2005,3,15), owner: < name: ‘Konstantin Volodin’, occupation: ‘bounty hunter’, >whatAmI: function() < return this.year + ‘ ‘ + this.make + ‘ ‘ + this.model; >>;

К первоначальному коду добавилось свойство с именем WhatAmI которое ссылается на экземпляр Function. Новая иерархия объектов показана на рисунке:

Рисунок 6. Эта модель ясно показывает, что функция не является частью объекта Object, она лишь доступна через свойство объекта, которое называется whatAmI

Если функция вызывается через свойство

var bike = ride.whatAmI();

то в качестве контекста функции (ссылка this) устанавливается экземпляр объекта, на который указывает ride. В результате в переменную bike записывается строка 2005 Yamaha V-Star Silverado, потому что функция выбирает с помощью this значения свойств объекта, посредством которого она была вызвана.

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

JS позволяет нам чётко установить, что будет использовано в качестве контекста функции. Мы можем передать в контексте функции всё, что угодно, вызвав функцию с помощью метода call() или apply() экземпляра Function.

Кроме того, будучи обычными объектами, даже функции имеют методы, определяемые конструктором Function.

Метод call() вызывает функцию, передавая в качестве первого параметра объект, который является контекстом функции, а в качестве остальных параметров передаются параметры вызываемой функции – вторым параметром метода call() становится первый аргумент вызываемой функции и т.д. Метод apply() работает аналогично, за исключением того, что вторым параметром он ожидает получить массив объектов, которые становятся аргументами вызываемой функции.

Пришло время для более обстоятельного примера.

  Function Context Example   

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

Затем мы определяем глобальную функцию, которая возвращает значение свойства handle для любого объекта, используемого в качестве контекста функции v, и присваиваем ту же самую функцию свойству identifyMe w объекта o1. Можно сказать, что тем самым был создан метод объекта o1 с именем identifyMe, хотя важно отметить, что функция объявляется независимо от объекта.

Наконец, мы выводим четыре предупреждения, каждый раз вызывая один и тот же экземпляр функции различным способом.

Эта последовательность сообщений иллюстрирует следующее:

  • Если функция вызывается как глобальная функция, контекстом функции является экземпляр объекта window x.
  • Если функция вызывается как свойство объекта (o1 в данном случае), контекстом функции становится этот объект y. Мы могли бы сказать, что функция действует как метод этого объекта – аналогично ОО языкам. Но это не совсем так.
  • Использование метода call() объекта Function приводит к тому, что контекстом функции становится любой объект, полученный методом call() в качестве первого параметра, — в данном случае о2 z. В этом примере функция действует как метод объекта o2 при том, что она никак не связана с объектом о2, даже как свойство.
  • Как и в случае с методом call(), при использовании метода apply() контекстом функции становится любой объект, переданный в качесвте первого параметра

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

Функция f действует как метод объекта о, когда объект о выступает в качестве контекста функции при вызове функции f.

В качестве дополнительной иллюстрации данной концепции рассмотрим результат добавления к нашему примеру следующей инструкции:

Даже при том, что мы ссылаемся на функцию, как на свойство объекта о1, роль контекста функции в этом вызове играет объект о3. Далее подчеркнём ещё раз, что дело не в том, как функция объявляется, а в том, как она вызывается , что и определяет контекст функции. Как мы рассмотрим далее в одной из следующих лекций, при использовании команд и функций jQuery применяются функции обратного вызова, что доказывает важность этой концепции.

Теперь, когда мы понимаем, каким образом функции могут действовать в качесвте методов объектов, перейдём к другой достаточно сложной теме, важной для эффективного использования jQuery, — к замыканиям.

Замыкание (closure) – это экземпляр Function вместе с локальными переменными из его окружения, необходимыми для выполнения.

Фабричные функции JavaScript

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

Фабричные функции в JavaScript всегда были притягательными, ведь они дают возможность легко производить экземпляры объектов без погружения в запутанность классов и ключевого слова “new” .

JavaScript предоставляет очень удобный объектный литеральный синтаксис. Это выглядит примерно так:

const user = userName: ‘echo’, 
avatar: ‘echo.png’
>;

Как и JSON (который основан на литеральной записи объектов JavaScript), левая часть : это имя свойства, а правая — значение. Доступ к свойствам можно получить через точку:

console.log(user.userName); // “echo”

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

const key = 'avatar';
console.log( user[key] ); // "echo.png"

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

const userName = 'echo';
const avatar = 'echo.png';
const user = userName,
avatar
>;
console.log(user); //

Объектные литералы поддерживают краткий синтаксис метода. Мы можем добавить метод .setUserName() :

const userName = 'echo';
const avatar = 'echo.png';
const user = userName,
avatar,
setUserName (userName) this.userName = userName;
return this;
>
>;
console.log(user.setUserName('Foo').userName); // "Foo"

В кратких методах, this относится к объекту, метод которого вызван. Чтобы вызвать метод в другом объекте, достаточно получить доступ к методу через точку, и вызвать метод с помощью скобок, таким образом game.play() применит .play() объекту game . Для того, чтобы применить метод вызванный через точку, этот метод должен быть свойством рассматриваемого объекта. Также можно применить метод к любому другому произвольному объекту, используя методы прототипа функций .call() , .apply() или .bind() .

В случае user.setUserName(‘Foo’) применит .setUserName() к user , таким образом this === user . В методе .setUserName() , мы устанавливаем значение свойства userName у объекта user через привязку контекста, и возвращаем тот же экземпляр объекта для цепочки методов.

Литералы для одного, Фабрики для многих

Если нужно создать много объектов, вам захочется скомбинировать возможности объектных литералов и фабричных функций.

С фабричными функциями можно создать сколько угодно объектов. Если вы разрабатываете приложение-чат, например, у вас имеется объект user , который представляет текущего пользователя, а также множество других объектов пользователей, которые авторизованы и ведут переписку в данный момент, таким образом, мы можем отобразить их имена и аватары.

Давайте превратим наш объект user в фабрику createUser() :

const createUser = (< userName, avatar >) => ( userName, 
avatar,
setUserName (userName) this.userName = userName;
return this;
>
>);
console.log(createUser(< userName: 'echo', avatar: 'echo.png' >));
/*
"avatar": "echo.png",
"userName": "echo",
"setUserName": [Function setUserName]
>
*/

Возвращение объектов

Стрелочные функции ( => ) имеют неявную функцию возврата: если тело функции состоит из единого выражения, ключевое слово return можно опустить: () => ‘foo’ — это функция, которая не принимает параметров и возвращает строку “Foo” .

Будьте осторожны, когда возвращаете объектные литералы. По умолчанию, JavaScript предполагает, что вы хотите создать тело функции при использовании фигурных скобок, например, . Если вы хотите использовать неявный возврат для литерала объекта, вам нужно устранить неоднозначность, заключив литерал объекта в круглые скобки:

const noop = () => < foo: 'bar' >;
console.log(noop()); // undefinedconst
createFoo = () => (< foo: 'bar' >);
console.log(createFoo()); //

В первом примере foo : интерпретируется как метка, и bar — как выражение, которое не присваивается и не возвращается. Функция возвращает undefined .

В примере createFoo() , скобки интерпретируются как выражение, которое может быть вызвано, а не как тело функции.

Деструктуризация

Обратите внимание на запись функции:

В этом примере, скобки ( < , >) представляют собой деструктуризацию объекта. Функция принимает один аргумент (объект), но извлекает два формальных параметра из единого аргумента — userName и avatar . Далее параметры могут использоваться, как переменные внутри области видимости тела функции. Также можно деструктурировать массивы:

const swap = ([first, second]) => [second, first];
console.log( swap([1, 2]) ); // [2, 1]

Вы можете использовать rest и spread синтаксис ( …varName ) , чтобы собрать остальные значения массива (или списка arguments), и затем объединить данные элементы массива в один элемент:

const rotate = ([first, . rest]) => [. rest, first];
console.log( rotate([1, 2, 3]) ); // [2, 3, 1]

Вычисляемые свойства ключей

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

const key = 'avatar';
console.log( user[key] ); // "echo.png"

Мы также можем вычислить значения, которые будут соответствовать ключам:

const arrToObj = ([key, value]) => (< [key]: value >);
console.log( arrToObj([ 'foo', 'bar' ]) ); //

В данном примере, arrToObj принимает массив, состоящий из пар ключ/значение, и конвертирует их в объект. Пока имя ключа нам неизвестно, нам нужно вычислить имя свойства, чтобы установить пару ключ/значения для объекта. Для этого, мы одолжим идею с квадратными скобками у вычисляемых свойств аксессоров, и использует в контексте создания литерала объекта:

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

Параметры по умолчанию

Функции в JavaScript поддерживают значение параметров по умолчанию, у которых есть несколько преимуществ:

1. Пользователи могут опускать параметры с подходящими значениями по умолчанию.

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

3. Среды IDE и инструменты статического анализа могут использовать значения по умолчанию для определения ожидаемого типа параметра. Например, значение по умолчанию 1 подразумевает, что параметр может принимать аргумент типа Number .

Используя параметры по умолчанию, мы можем задокументировать ожидаемый интерфейс для нашей фабрики createUser и автоматически заполнить ‘Anonymous’ данные, если информация о пользователе не указана:

const createUser = ( userName = 'Anonymous', 
avatar = 'anon.png'
> = <>) => ( userName,
avatar
>);
console.log(
// < userName: "echo", avatar: 'anon.png' >
createUser(< userName: 'echo' >),
// < userName: "Anonymous", avatar: 'anon.png' >
createUser()
);

Сигнатура последней части функции, вероятно, выглядит забавно:

Последнее = <> , в перечне параметров, перед закрывающей скобкой, означает то, что если аргументы не переданы — по умолчанию будет использован пустой объект. Когда вы попробуете деструктурировать значения из пустого объекта — автоматически будут использованы значения по умолчанию, потому что — это именно то, что значения по умолчанию делают — замещают undefined некоторым предопределенным значением.

Без = <> присвоения аргумента по умолчанию, createUser() без аргументов выбросит ошибку ,так как мы не можем обратиться к свойствам из undefined .

Вывод типа

На момент написания статьи в JavaScript не было никаких примечаний к нативному типу, но за эти годы появилось несколько конкурирующих форматов, чтобы заполнить пробелы, в том числе JSDoc (в упадке из-за появления лучших вариантов), Facebook Flow и Microsoft TypeScript. Я использую rtype для документации — нотация, которую я нахожу намного более читабельной, чем TypeScript для функционального программирования.

На момент написания этой статьи не было явного победителя для аннотаций типов. Ни одна из альтернатив не была благословлена спецификацией JavaScript, и, похоже, во всех них есть явные недостатки.

Вывод типа — это процесс вывода типов на основе контекста, в котором они используются. В JavaScript это очень хорошая альтернатива аннотациям типов.

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

Даже если вы решите использовать такой инструмент, как TypeScript или Flow, вы должны делать как можно больше с выводом типа и сохранять аннотации типов для ситуаций, когда вывод типа не выполняется. Например, в JavaScript нет собственного способа указать общий интерфейс. Это легко и полезно с TypeScript или rtype.

Tern.js — это популярный инструмент вывода типов для JavaScript, который имеет плагины для многих редакторов кода и IDE.

Код Microsoft Visual Studio не нуждается в Tern, потому что он обеспечивает возможности вывода типов из TypeScript в обычный код JavaScript.

Когда вы задаете параметры по умолчанию для функций в JavaScript, инструменты, способные к выводу типов, такие как Tern.js, TypeScript и Flow, могут предоставлять подсказки IDE, чтобы помочь вам правильно использовать API, с которым вы работаете.

Без значений по умолчанию у IDE (и часто у людей) недостаточно подсказок, чтобы определить ожидаемый тип параметра.

По умолчанию IDE (и часто люди) могут выводить типы из примеров.

Не всегда имеет смысл ограничивать параметр фиксированным типом (что затруднит универсальные функции и функции более высокого порядка), но когда это имеет смысл, параметры по умолчанию часто являются лучшим способом сделать это, даже если вы Вы используете TypeScript или Flow.

Фабричные функции для миксинов

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

Вот где сияют функциональные миксины. Давайте создадим миксин withConstructor , чтобы добавить свойство .constructor ко всем экземплярам объекта.

const withConstructor = constructor => o => ( // create the delegate [[Prototype]] 
__proto__: // add the constructor prop to the new [[Prototype]]
constructor
>,
// mix all o’s props into the new object
…o
>);

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

import withConstructor from `./with-constructor';const pipe = (. fns) => x => fns.reduce((y, f) => f(y), x);
// or `import pipe from 'lodash/fp/flow';`
// Set up some functional mixins
const withFlying = o => let isFlying = false; return . o,
fly () isFlying = true;
return this;
>,
land () isFlying = false;
return this;
>,
isFlying: () => isFlying
>
>;
const withBattery = (< capacity >) => o => let percentCharged = 100; return . o,
draw (percent) const remaining = percentCharged - percent;
percentCharged = remaining > 0 ? remaining : 0;
return this;
>,
getCharge: () => percentCharged,
getCapacity: () => capacity
>;
>;
const createDrone = (< capacity = '3000mAh' >) => pipe(
withFlying,
withBattery(< capacity >),
withConstructor(createDrone)
)(<>);
const myDrone = createDrone(< capacity: '5500mAh' >);console.log(`
can fly: $< myDrone.fly().isFlying() === true >
can land: $< myDrone.land().isFlying() === false >
battery capacity: $< myDrone.getCapacity() >
battery status: $< myDrone.draw(50).getCharge() >%
battery drained: $< myDrone.draw(75).getCharge() >% remaining
`);
console.log(`
constructor linked: $< myDrone.constructor === createDrone >
`);

Как видите, повторно используемый миксин withConstructor() просто добавляется в конвейер вместе с другими миксинами. withBattery() может использоваться с другими объектами, такими как роботы, электрo скейтборды или зарядные устройства для портативных устройств. withFlying() может использоваться для моделирования летающих машин, ракет или воздушных шаров.

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

Заключение

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

В JavaScript классы более многословны и ограничительны, чем фабрики, и являются чем-то вроде минного поля, когда речь заходит о рефакторинге, но они также были приняты крупными интерфейсными средами, такими как React и Angular, и есть пара редких применений — случаи, которые делают сложность стоящей.

«Иногда элегантная реализация — это просто функция. Не метод. Не класс. Не фреймворк. Просто функция ». ~ Джон Кармак

Начните с самой простой реализации и переходите к более сложной реализации только по мере необходимости.

Значения, переменные и литералы.

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

В главе имеются следующие разделы:

1. Значения

JavaScript распознаёт значения следующих типов:

  • Числа, такие как 42 или 3.14159
  • Логические (Булевы) значения: true или false.
  • Строки, такие как “Howdy!”.
  • null, специальное ключевое слово, обозначающее нулевое значение; null является также примитивным значением. Поскольку JavaScript чувствителен к регистру символов, null это не то же самое, что Null, NULL или какой-нибудь иной вариант.
  • undefined, свойство верхнего уровня, значение которого не определено/undefined; undefined является также примитивным значением.

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

1.1. Конверсия типов данных

JavaScript это динамически типизированный язык. Это означает, что Вы не должны специфицировать тип данных переменной при её объявлении и что типы данных при необходимости автоматически конвертируются в процессе выполнения скрипта. Так, например, Вы можете определить переменную:

var answer = 42

и позднее можете присвоить этой же переменной строковое значение:

answer = "Thanks for all the fish. "

Поскольку JavaScript типизируется динамически, такое присвоение не является ошибкой.

В выражениях, содержащих числа и строки и операцию +, JavaScript конвертирует числа в строки. Например:

x = "The answer is " + 42 // возвращает "The answer is 42" y = 42 + " is the answer" // возвращает "42 is the answer"

В выражениях с использованием других операций, JavaScript не конвертирует числа в строки. Например:

"37" - 7 // возвращает 30 "37" + 7 // возвращает 377

2. Переменные

Вы используете переменные в качестве символических имён значений. Имена переменных, называемые идентификаторы, подчиняются определённым правилам.

Идентификатор в JavaScript обязан начинаться с латинской буквы, символа подчёркивания/underscore (_) или знака доллара ($); последующие символы имени могут также быть цифрами ( 0–9 ). Поскольку JavaScript различает регистр символов, могут использоваться буквы от “A” до “Z” (верхний регистр) и от “a” до “z” (нижний регистр).

Начиная с JavaScript 1.5, Вы можете использовать в качестве идентификаторов буквы ISO 8859–1 или Unicode, такие как å и ü. Вы можете также использовать в качестве символов в идентификаторах escape-последовательности \uXXXX Unicode.

Вот примеры правильных имён: Number_hits, temp99, _name.

2.1. Объявление переменных

Можно объявить переменную двумя способами:

  • Просто присвоив ей значение. Например, x = 42
  • С помощью ключевого слова var. Например, var x = 42

2.2. Вычисление переменных

Переменная или элемент массива, которым не присвоены значения, имеют значение undefined. Результат вычисления переменной с неприсвоенным значением зависит от того, как она была объявлена:

  • Если такая переменная была объявлена без использования var, возникает ошибка времени выполнения.
  • Если такая переменная была объявлена с использованием var, результатом будет значение undefined, или ##NaN## в числовом контексте.
function f1()  return y - 2; > f1() //Вызывает ошибку времени выполнения/runtime error function f2()  return var y - 2; > f2() //возвращает NaN

Вы можете использовать undefined для определения, имеет ли переменная значение. В следующем коде переменная input не имеет присвоенного значения, и оператор if вычисляется в true.

var input; if(input === undefined) doThis(); > else  doThat(); >

Значение undefined ведёт себя как false, если используется с булевым значением. Например, следующий код выполняет функцию myFunction, поскольку элемент массива не определён:

myArray=new Array() if (!myArray[0]) myFunction()

Если Вы вычисляете null-переменную, значение null ведёт себя как 0 в числовом контексте и как false – в булевом. Например:

var n = null n * 32 //возвращает 0

2.3. Область видимости/Scope переменной

Если Вы устанавливаете идентификатор переменной путём присваивания значения вне функции, такая переменная называется глобальной, поскольку доступна в любом месте текущего документа. Если Вы объявляете переменную внутри функции, она называется локальной, поскольку «видна»/доступна только внутри этой функции.

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

Вы можете обращаться к переменным, объявленным в одном окне или фрэйме, из другого окна или фрэйма, специфицируя имя окна или фрэйма. Например, если переменная phoneNumber объявлена во FRAMESET-документе, Вы можете обратиться к ней из дочернего фрэйма: parent.phoneNumber.

3. Константы

Вы можете создавать именованные константы «только-для-чтения» с помощью ключевого слова const. Синтаксис идентификатора константы такой же, что и у идентификатора переменной: он обязан начинаться с буквы или символа подчёркивания и может содержать символы алфавита, числа и символ подчёркивания.

const prefix = '212';

Константа не может изменить значение путём нового присвоения и не может быть переобъявлена в процессе выполнения скрипта.

Правила видимости для констант те же, что и для переменных, за исключением того, что ключевое слово const необходимо всегда, даже для глобальных констант. Если ключевое слово отсутствует, идентификатор представляет var.

Вы не можете объявить константу с тем же именем, что и у функции или переменной, в одной области видимости с этой функцией или переменной. Например:

//ЭТО ВЫЗОВЕТ ОШИБКУ function f<>; const f = 5; //ЭТО ТАКЖЕ ВЫЗОВЕТ ОШИБКУ function f const g=5; var g; //операторы >

4. Литералы

Для представления значения в JavaScript можно использовать литералы. Они являются фиксированными значениями, не переменными, которые Вы литерально\буквально предоставляете в Вашем скрипте.

В этом разделе описаны следующие типы литералов:

4.1. Литералы массива

Литерал массива это заключённый в квадратные скобки ([]) список из нуль или более выражений, каждое из которых представляет элемент массива. Если Вы создаёте массив с использованием литерала массива, этот массив инициализируется специфицированными значениями в качестве элементов, и его размер равен количеству специфицированных аргументов.

В следующем примере создаётся массив coffees из трёх элементов размером «три»:

coffees = ["French Roast", "Columbian", "Kona"]

Примечание

Литерал массива это тип инициализатора объекта. См. «Использование Инициализаторов Объектов».

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

Литералы массива являются также Array-объектами. См. в разделе «Объект Array» детали об Array-объектах.

Дополнительные запятые в литералах массивов

Вам не нужно специфицировать все элементы в литерале массива. Если Вы поместите две запятые в литерал, массив будет создан с пространством для unspecified(неспецифицированных)-элементов.
В следующем примере создаётся массив fish:

fish = ["Lion", , "Angel"]

В этом массиве имеются два элемента со значениями и один пустой элемент (fish[0] это “Lion”, fish[1] – undefined и fish[2] – “Angel”).

Если Вы вставите запятую в конце списка элементов, она игнорируется. В следующем примере размер массива – «три». Элемента myList[3] нет. Две другие запятые в списке обозначают новый элемент.

myList = ['home', , 'school', ];

В следующем примере размер массива – «четыре», а myList[0] и myList[2] пропущены.

myList = [ , 'home', , 'school'];

В следующем примере размер массива – «четыре», а myList[1] и myList[3] пропущены. Игнорируется только последняя запятая. Эта ведомая запятая не обязательна.

myList = ['home', , 'school', , ];

4.2. Булевы литералы

Тип Boolean имеет два литеральных значения: true и false.

Не путайте примитивные Boolean-значения true и false со значениями true и false объекта Boolean. Объект Boolean является «обёрткой»-оболочкой примитивного типа данных Boolean. См. «Объект Boolean».

4.3. Литералы с плавающей точкой

Литерал с плавающей точкой может состоять из следующих частей:

  • 10-ричного целого числа
  • Десятичной точки (".")
  • Дробной части (другого 10-ричного числа)
  • Экспоненты

Примеры литералов с плавающей точкой: 3.1415, -3.1E12, .1e12, 2E-12.

4.4. Целочисленные литералы

Целые числа могут иметь десятеричную базу (база 10), 16-ричную (база 16) и 8-ричную (база 8). Десятеричный целочисленный литерал состоит из последовательности цифр без ведущего 0 (нуля). Ведущий 0 (нуль) в целочисленном литерале означает, что это 8-ричный литерал; ведущие символы 0x (или 0X) означают 16-ричный. 16-ричные целые числа могут состоять из цифр (0 – 9) и букв a-f и A-F. 8-ричные целые могут состоять только из цифр 0 – 7.

8-ричные целочисленные литералы использовать не рекомендуется, и они будут удалены из стандарта ECMA-262, Edition 3. JavaScript 1.5 ещё поддерживает их для обеспечения обратной совместимости.

Примеры целочисленных литералов: 42, 0xFFF, -345.

4.5. Литералы объектов

Литерал объекта это заключённый в фигурные скобки (<>) список из 0 или более пар свойств объекта и ассоциированных с ними значений. Вы не должны использовать литерал объекта в начале оператора. Это приведёт к ошибке, или поведение будет не таким, какое Вы ожидаете, поскольку < будет интерпретироваться как начало блока.

Вот пример литерала объекта. Первый элемент объекта car определяет свойство myCar; второй элемент – свойство getCar, вызывающее функцию (CarTypes("Honda")); третий элемент, специальное свойство, использует существующие переменные (Sales).

var Sales = "Toyota"; function CarTypes(name)  if(name == "Honda") return name; else return "Sorry, we don't sell " + name + "."; > car = myCar: "Saturn", getCar: CarTypes("Honda"), special: Sales> document.write(car.myCar); // Saturn document.write(car.getCar); // Honda document.write(car.special); // Toyota

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

car = manyCars: a: "Saab", b: "Jeep">, 7: "Mazda"> document.write(car.manyCars.b); // Jeep document.write(car[7]); // Mazda

4.6. Строковые литералы

Строковой литерал это 0 или более символов, заключённых в двойные (") или одинарные (') кавычки. Строка обязана быть ограничена кавычками одного вида; то есть, оба знака – двойные, или оба знака – одинарные кавычки.
Примеры строковых литералов:

  • “blah”
  • 'blah'
  • “1234”
  • «одна строка \n вторая строка»

Вы, как правило, должны использовать строковой литерал, если только Вам не нужно использовать именно String-объект. См. в разделе «Объект String» детальную информацию о String-объектах.

Использование специальных символов в строках

Кроме обычных символов, Вы можете включать в строки также и специальные символы, как в это примере:

«одна строка \n вторая строка»

В следующей таблице дан список специальных символов, которые Вы можете использовать в строках JavaScript.

Таблица 2.1 Специальные символы JavaScript

Символ Значение
\b Backspace
\f Form feed/Прогон страницы
\n New line/Символ новой строки
\r Carriage return/Возврат каретки
\t Tab / Табуляция ?
\v Vertical tab/Вертикальная табуляция
\' Апостроф или одинарная кавычка
\" Двойная кавычка
\\ Символ «обратная наклонная линия»Backslash (\)
\XXX Символ в кодировке Latin -1, специфицированный тремя 8-ричными цифрами XXX в диапазоне от 0 до 377. Например, \251 это 8-ричная последовательность для символа copyright.
\xXX Символ в кодировке Latin -1, специфицированный двумя 16-ричными цифрами XX от 00 до FF. Например, \xA9 это 16-ричная последовательность для символа copyright.
\uXXXX Символ Unicode, специфицированный четырьмя 16-ричными цифрами XXXX. Например, \u00A9 это Unicode-последовательность для символа copyright. См. Escape-Последовательности Unicode.

Заменяющие (Escaping) символы

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

Вы можете вставить знак кавычки в строку, предварив его символом backslash. Это известно как escaping (замена) знака кавычки. Например,

var quote = "He read \"The Cremation of Sam McGee\" by R.W. Service." document.write(quote)

В результате будет выведено

He read «The Cremation of Sam McGee" by R.W. Service.

Чтобы включить в строку символ backslash, Вы обязаны escape'ировать (подменить) символ backslash. Например, чтобы ввести в строке путь c:\temp, используйте:

var home = "c:\\temp"

5. Unicode

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

Набор символов Unicode может использоваться для всех известных кодировок. Unicode смоделирован после ASCII-набора символов (American Standard Code for Information Interchange). Он использует числовое значение и имя для каждого символа. Кодировка символов специфицирует идентификатор символа и его числовое значение (кодовую позицию), а также битовое представление этого значения. 16-битное числовое значение (кодовое значение) определяется 16-ричным числом и префиксом U, например, U+0041 представляет символ A. Уникальное имя этого символа – LATIN CAPITAL LETTER A.

Версии JavaScript до 1.3.

Unicode не поддерживается в версиях, более ранних, чем JavaScript 1.3.

5.1. Совместимость Unicode с ASCII и ISO

Unicode совместим с символами ASCII и поддерживается многими программами. Первые 128 символов Unicode соответствуют набору ASCII и имеют те же битовые значения. Символы Unicode с U+0020 до U+007E эквивалентны ASCII-символам с 0x20 по 0x7E. В отличие от ASCII, который поддерживает латинский алфавит и использует набор 7-битных символов, Unicode использует 16-битное значение для каждого символа. Это даёт возможность отобразить десятки тысяч символов. Поддерживается также механизм расширения, UTF-16, который позволяет кодировать более миллиона символов путём использования пар 16-битных символов. UTF переводит кодировку в реальные биты.

Unicode полностью совместим с Международным Стандартом ISO/IEC 10646–1 ; 1993, который является поднабором ISO 10646, а также поддерживает ISO UCS-2 (Universal Character Set), который использует двухбайтные значения (два байта, или 16 битов).

JavaScript и Navigator поддерживают Unicode, Это означает, что Вы можете использовать не-Latin, международные и локализованные символы, плюс специальные технические символы в программах на JavaScript. Unicode предоставляет возможность стандартного кодирования многоязыковых текстов. Поскольку Unicode совместим с ASCII, программы могут использовать символы ASCII. Вы можете использовать не-ASCII Unicode-символы в комментариях, строковых литералах, идентификаторах и регулярных выражениях JavaScript.

5.2. Escape-последовательности Unicode

Вы можете использовать заменяющие последовательности Unicode в строковых литералах, регулярных выражениях и идентификаторах. Заменяющая (escape) последовательность состоит из шести символов ASCII: \u и 16-ричного числа из четырёх цифр. Например, \u00A9 представляет символ copyright. Каждая escape-последовательность Unicode интерпретируется в JavaScript как одиночный символ.

Следующий код возвращает символ copyright и строку “Netscape Communications”:

x="\u00A9 Netscape Communications"

В таблице приведён список часто используемых специальных символов и их значения Unicode.

Таблица 2.2 Unicode-значения специальных символов

Категория Значение Unicode Имя Форматное имя
Пробельные символы \u0009 Табуляция
\u000B Вертикальная табуляция
\u000C Прогон страницы
\u0020 Пробел
Значения конца строки \u000A Прогон строки
\u000D Возврат каретки
Дополнительные последовательности Unicode \u0008 Backspace
\u0009 Горизонтальная табуляция
\u0022 Двойная кавычка »
\u0027 Одинарная кавычка '
\u005C Backslash \

Использование escape-последовательнотей Unicode в JavaScript отличается от Java. В JavaScript escape-последовательность сразу никогда не интерпретируется как спецсимвол. Например, последовательность терминатора строки внутри строки не оканчивает строку до того, как она будет интерпретирована функцией. JavaScript игнорирует любую escape-последовательность, если она находится в комментарии. В Java, если escape-последовательность используется в однострочном комментарии, она интерпретируется как символ Unicode. В строковом литерале компилятор Java сначала интерпретирует escape-последовательности. Например, если последовательность «терминатор строки» (\u000A) используется в Java, она оканчивает строковой литерал. В Java это приводит к ошибке, поскольку терминаторы строки не допускаются в строковых литералах. Вы обязаны использовать \n в строковом литерале в качестве символа новой строки. В JavaScript escape-последовательность работает так же, как \n.

5.3. Отображение символов Unicode

Вы можете использовать Unicode для отображения символов на разных языках или технических символов. Чтобы правильно отображать символы, клиент, такой как Netscape Navigator 4.x или Netscape 6, должен поддерживать Unicode. Кроме того, нужный Unicode-шрифт обязан быть доступен клиенту, а клиентская платформа (ОС) обязана поддерживать Unicode. Часто шрифты Unicode не отображают всех символов Unicode. Некоторые платформы, такие как Windows 95, предоставляют частичную поддержку Unicode.

Чтобы принять ввод не-ASCII символов, необходимо пересылать ввод клиенту как Unicode. Используя стандартную расширенную клавиатуру, клиент не может легко ввести дополнительные символы, поддерживаемые Unicode. Иногда единственным способом ввести символы Unicode будет использование escape-последовательностей Unicode.

Дополнительную информацию о Unicode см. на вэб-сайте Консорциума Unicode и в книге The Unicode Standard, Version 2.0, опубликованной издательством Addison-Wesley в 1996 году.

Стрелочные функции в JavaScript: зачем они нужны, как с ними обращаться, когда ими стоит пользоваться, а когда — нет

Одной из самых заметных новшеств современного JavaScript стало появление стрелочных функций (arrow function), которые иногда называют «толстыми» стрелочными функциями (fat arrow function). При объявлении таких функций используют особую комбинацию символов — => .

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

image

Иногда эти и другие преимущества ведут к тому, что стрелочному синтаксису отдают безусловное предпочтение перед другими способами объявления функций. Например, популярная конфигурации eslint от Airbnb принуждает к тому, чтобы всегда, когда создают анонимную функцию, такая функция была бы стрелочной.

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

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

Особенности стрелочных функций в JavaScript

Стрелочные функции в JavaScript — это нечто вроде лямбда-функций в Python и блоков в Ruby.

Это — анонимные функции с особым синтаксисом, которые принимают фиксированное число аргументов и работают в контексте включающей их области видимости, то есть — в контексте функции или другого кода, в котором они объявлены.

Поговорим об этом подробнее.

▍Синтаксис стрелочных функций

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

(argument1, argument2, . argumentN) => < // тело функции >

Список аргументов функции находится в круглых скобках, после него следует стрелка, составленная из символов = и > , а дальше идёт тело функции в фигурных скобках.

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

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

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

const add = (a, b) => a + b;

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

const getFirst = array => array[0];

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

▍Возврат объектов и сокращённая запись стрелочных функций

При работе со стрелочными функциями используются и некоторые более сложные синтаксические конструкции, о которых полезно знать.

Например, попытаемся использовать однострочное выражение для возврата из функции объектного литерала. Может показаться, учитывая то, что мы уже знаем о стрелочных функциях, что объявление функции будет выглядеть так:

(name, description) => ;

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

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

(name, description) => ();

▍Стрелочные функции и включающий их контекст выполнения

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

На практике это означает, что они наследуют сущности this и arguments от родительской функции.

Например, сравним две функции, представленные в следующем коде. Одна и них обычная, вторая — стрелочная.

const test = < name: 'test object', createAnonFunction: function() < return function() < console.log(this.name); console.log(arguments); >; >, createArrowFunction: function() < return () =>< console.log(this.name); console.log(arguments); >; > >;

Тут имеется объект test с двумя методами. Каждый из них представляет собой функцию, которая создаёт и возвращает анонимную функцию. Разница между этими методами заключается лишь в том, что в первом из них используется традиционное функциональное выражение, а во втором — стрелочная функция.

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

> const anon = test.createAnonFunction('hello', 'world'); > const arrow = test.createArrowFunction('hello', 'world'); > anon(); undefined <> > arrow(); test object

У анонимной функции есть собственный контекст, поэтому, когда её вызывают, при обращении к test.name не будет выдано значение свойства name объекта, а при обращении к arguments не будет выведен список аргументов функции, которая использовалась для создания и возврата исследуемой функции.

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

Ситуации, в которых стрелочные функции улучшают код

▍Обработка списков значений

Традиционными лямбда-функциями, а также — стрелочными функциями, после появления их в JavaScript, обычно пользуются в ситуации, когда некая функция применяется к каждому элементу некоего списка.

Например, если имеется массив значений, который нужно преобразовать с использованием метода массивов map , для описания такого преобразования идеально подходит стрелочная функция:

const words = ['hello', 'WORLD', 'Whatever']; const downcasedWords = words.map(word => word.toLowerCase());

Вот чрезвычайно распространённый пример подобного использования стрелочных функций, который заключается в работе со свойствами объектов:

const names = objects.map(object => object.name);

Аналогично, если вместо традиционных циклов for используют современные циклы forEach , основанные на итераторах, то, что стрелочные функции используют this родительской сущности, делает их использование понятным на интуитивном уровне:

this.examples.forEach(example => < this.runExample(example); >);

▍Промисы и цепочки промисов

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

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

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

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

this.doSomethingAsync().then((result) => < this.storeResult(result); >);

▍Трансформация объектов

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

Например, в Vue.js существует общепринятый паттерн включения фрагментов хранилища Vuex напрямую в компонент Vue с использованием mapState .

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

Такие вот простые преобразования — идеальное место для использования стрелочных функций. Например:

export default < computed: < . mapState(< results: state =>state.results, users: state => state.users, >); > >

Ситуации, в которых не следует использовать стрелочные функции

▍Методы объектов

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

Первая такая ситуация заключается в использовании стрелочных функций в качестве методов объектов. Здесь важны контекст выполнения и ключевое слово this , характерные для традиционных функций.

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

class Counter < counter = 0; handleClick = () => < this.counter++; >>

При использовании подобной конструкции, даже если функция handleClick вызывалась обработчиком событий, а не в контексте экземпляра класса Counter , у этой функции был доступ к данным этого экземпляра.

Однако у такого подхода масса минусов, которым посвящён этот материал.

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

В подобных случаях, вместо стрелочных функций, используйте обычные функции, и, если нужно, привязывайте к ним экземпляр объекта в конструкторе:

class Counter < counter = 0; handleClick() < this.counter++; >constructor() < this.handleClick = this.handleClick.bind(this); >>

▍Длинные цепочки вызовов

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

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

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

▍Функции с динамическим контекстом

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

Если в подобных ситуациях применяются стрелочные функции, то динамическая привязка this работать не будет. Эта неприятная неожиданность может заставить поломать голову над причинами происходящего тех, кому придётся работать кодом, в котором стрелочными функциями пользуются неправильно.

Вот некоторые вещи, о которых нужно помнить, рассматривая возможность использования стрелочных функций:

  • Обработчики событий вызываются с this , привязанным к атрибуту события currentTarget .
  • Если вы всё ещё пользуетесь jQuery, учитывайте, что большинство методов jQuery привязывают this к выбранному элементу DOM.
  • Если вы пользуетесь Vue.js, то методы и вычисляемые функции обычно привязывают this к компоненту Vue.

Итоги

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

Уважаемые читатели! Сталкивались ли вы с ситуациями, в которых использование стрелочных функций приводит к ошибкам, неудобствам или к неожиданному поведению программ?

  • Блог компании RUVDS.com
  • Веб-разработка
  • JavaScript

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *