Работа с объектами
JavaScript спроектирован на основе простой парадигмы. В основе концепции лежат простые объекты. Объект — это набор свойств, и каждое свойство состоит из имени и значения, ассоциированного с этим именем. Значением свойства может быть функция, которую можно назвать методом объекта. В дополнение к встроенным в браузер объектам, вы можете определить свои собственные объекты. Эта глава описывает как пользоваться объектами, свойствами, функциями и методами, а также как создавать свои собственные объекты.
Обзор объектов
Объекты в JavaScript, как и во многих других языках программирования, похожи на объекты реальной жизни. Концепцию объектов JavaScript легче понять, проводя параллели с реально существующими в жизни объектами.
В JavaScript объект — это самостоятельная единица, имеющая свойства и определённый тип. Сравним, например, с чашкой. У чашки есть цвет, форма, вес, материал, из которого она сделана, и т.д. Точно так же, объекты JavaScript имеют свойства, которые определяют их характеристики.
Объекты и свойства
В JavaScript объект имеет свойства, ассоциированные с ним. Свойство объекта можно понимать как переменную, закреплённую за объектом. Свойства объекта в сущности являются теми же самыми переменными JavaScript, за тем исключением, что они закреплены за объектом. Свойства объекта определяют его характеристики. Получить доступ к свойству объекта можно с помощью точечной записи:
Неопределённые свойства объекта являются undefined (а не null ).
Свойства объектов JavaScript также могут быть доступны или заданы с использованием скобочной записи (более подробно см. property accessors). Объекты иногда называются ассоциативными массивами, поскольку каждое свойство связано со строковым значением, которое можно использовать для доступа к нему. Так, например, вы можете получить доступ к свойствам объекта myCar следующим образом:
Имена свойств объекта могут быть строками JavaScript, или тем, что может быть сконвертировано в строку, включая пустую строку. Как бы то ни было, доступ к любому имени свойства, которое содержит невалидный JavaScript идентификатор (например, имя свойства содержит в себе пробел и тире или начинается с цифры), может быть получен с использованием квадратных скобок. Этот способ записи также полезен, когда имена свойств должны быть динамически определены (когда имя свойства не определено до момента исполнения). Примеры далее:
Вы также можете получить доступ к свойствам, используя значение строки, которое хранится в переменной:
Вы можете пользоваться квадратными скобками в конструкции for. in чтобы выполнить итерацию всех свойств объекта, для которых она разрешена. Чтобы показать как это работает, следующая функция показывает все свойства объекта, когда вы передаёте в неё сам объект и его имя как аргументы функции:
Так что если вызвать эту функцию вот так showProps(myCar, «myCar»), то получим результат:
Перечисление всех свойств объекта
Начиная с ECMAScript 5, есть три способа перечислить все свойства объекта (получить их список):
До ECMAScript 5 не было встроенного способа перечислить все свойства объекта. Однако это можно сделать с помощью следующей функции:
Это может быть полезно для обнаружения скрытых (hidden) свойств (свойства в цепочке прототипа, которые недоступны через объект, в случае, если другое свойство имеет такое же имя в предыдущем звене из цепочки прототипа). Перечислить доступные свойства можно, если удалить дубликаты из массива.
Создание новых объектов
Использование инициализаторов объекта
Помимо создания объектов с помощью функции-конструктора вы можете создавать объекты и другим, особым способом. Фактически, вы можете записать объект синтаксически, и он будет создан интерпретатором автоматически во время выполнения. Эта синтаксическая схема приведена ниже:
Если объект создан при помощи инициализатора объектов на высшем уровне скрипта, то JavaScript интерпретирует объект каждый раз, когда анализирует выражение, содержащее объект, записанный как литерал. Плюс, если пользоваться функцией инициализатором, то он будет создаваться каждый раз, когда функция вызывается.
Следующий пример создаёт объект myHonda с тремя свойствами. Заметьте, что свойство engine — это также объект со своими собственными свойствами.
Вы также можете использовать инициализатор объекта для создания массивов. Смотрите array literals.
До JavaScript 1.1 не было возможности пользоваться инициализаторами объекта. Единственный способ создавать объекты — это пользоваться функциями-конструкторами или функциями других объектов, предназначенных для этой цели. Смотрите Using a constructor function.
Использование функции конструктора
Другой способ создать объект в два шага описан ниже:
Заметьте, что используется this чтобы присвоить значения (переданные как аргументы функции) свойствам объекта.
Эта инструкция создаёт объект типа Car со ссылкой mycar и присваивает определённые значения его свойствам. Значением mycar.make станет строка «Eagle», mycar.year — это целое число 1993, и так далее.
Объект может иметь свойство, которое будет другим объектом. Например, далее определяется объект типа Person следующим образом:
и затем создать два новых экземпляра объектов Person как показано далее:
Затем, чтобы создать экземпляры новых объектов, выполните следующие инструкции:
Заметьте, что вместо того, чтобы передавать строку, литерал или целое число при создании новых объектов, в выражениях выше передаются объекты rand и ken как аргумент функции. Теперь, если вам нужно узнать имя владельца car2, это можно сделать следующим образом:
Заметьте, что в любое время вы можете добавить новое свойство ранее созданному объекту. Например, выражение
Использование метода Object.create
Наследование
Все объекты в JavaScript наследуются как минимум от другого объекта. Объект, от которого произошло наследование называется прототипом, и унаследованные свойства могут быть найдены в объекте prototype конструктора.
Индексы свойств объекта
В JavaScript 1.0 вы можете сослаться на свойства объекта либо по его имени, либо по его порядковому индексу. В JavaScript 1.1 и позже, если вы изначально определили свойство по имени, вы всегда должны ссылаться на него по его имени, и если вы изначально определили свойство по индексу, то должны ссылаться на него по его индексу.
Функция и функциональный объект
Написано с участием Игоря Катричека katrichek@gmail.com
Человек и все живые существа используют окружающую среду в своих целях. Считается что, построив парадигму объектов и, рассматривая созданные им объекты через призму их полезности, человек достиг в этом большего совершенства, чем животные. Например, отполировав медную пластину до зеркального блеска, человек использует свойство созданного им предмета отражать свет и называет эту пластину зеркалом. Такое отношение к среде порождает разделение объектов на полезные, вредные и никчемные. Создали ли животные парадигму объектов, или у них своя, отличная от нас парадигма, мы не знаем, спросить не можем.
По факту, все объекты — это части окружающей нас среды. Нет различия между самолетом и камнем. Нет разницы между объектами, в создании которых принимал человек и теми, в создании которых он не участвовал. Чтобы не тратить время на бесполезные споры о степени участия человека в создании тех или иных объектов, я предлагаю не делать различие между ними.
Понятие о функции
Любой объект можно наделить функцией. Например, зная те потоки, которые исчезают в теле объекта, и те потоки, которые образуются в нем, мы можем сказать, что функция объекта — преобразование входящих потоков в выходящие (например, у зеркала функция отражать падающий свет). Мы можем наделить объект функцией быть самим собой. И тогда функция объекта — это поток его состояний (например, функция картины демонстрировать себя). Правда, при этом мы учитываем не все потоки и не все состояния, а только те из них, которые либо желанны, либо необходимы с нашей точки зрения, например, для объяснения причин их возникновения.
Такое описание, в котором мы сконцентрированы только на желаемых и необходимых потоках материи, энергии и состояний, мы называем функцией.
Есть мнение, что функция связана только с потоками энергии или материи. Но такое представление о функции ничего не говорит о желаемом или необходимом потоке состояний. Поэтому я считаю, что определение функции надо дополнить желаемым потоком состояний. В определении функции не уточняется, есть ли она преобразование потоков, или просто их излучение и поглощение. Понятно, что преобразование не есть излучение. Можно попытаться исследовать этот вопрос с помощью примеров. Например, функция музыкального центра — играть музыку. В этом определении нет ничего про преобразование потоков. Именно поэтому я буду настаивать на том, что функция не связана с преобразованием потоков, но есть лишь их поглощение и излучение.
Субъективность функции
Поскольку желания и необходимости для каждого субъекта индивидуальны, то представление о желаемых и необходимых потоках — тоже субъективно. Поэтому очень важно помнить, что функция всегда субъективна. Она отражает субъективный взгляд на мир. Два субъекта, глядя на один и тот же объект, могут сделать разные выводы о его функциональном назначении.
Именно эта субъективность в определении функции заставила системную инженерию разделить объекты на те, которые создала природа, и на те, которые создал человек. Якобы те, что сделал человек, имеют функцию, а те, которые создала природа, не имеют оной. На это есть два возражения:
Поэтому такое деление изначально обречено на неудачу. Нет разницы между объектами, есть разница в трактовке потоков, связанных с этими объектами.
Связь между объектом и функцией
Изучая потоки, мы строим объекты. Как я писал ранее, потоки первичны, объекты, построенные на их основе — вторичны. Поэтому мы можем исследовать потоки, не строя при этом объекты. Мы лишь ограничиваем те области пространства, в которых происходит поглощение, испускание, передача потоков. Далее применяем к этим областям наше представление о желаемых и необходимых потоках. Так рождаются функции, не связанные с объектами. В такой интерпретации функция — это область пространства, в которой происходят поглощения, испускания или передачи желаемых потоков, а также потоков, которые могут объяснить эти поглощения, испускания или передачи. Замечу, что для такого подхода построение потока состояний невозможно, поскольку нет выделенного объекта.
Мы можем построить объект и задуматься над тем, для чего он предназначен. Тогда мы можем посмотреть на потоки, которые поглощаются и излучаются в его теле или с его поверхности, можем посмотреть на поток его состояний. Если эти потоки могут показаться нам полезными или необходимыми, мы создаем в своем воображении функцию, которая связана с этим объектом. Так рождается понятие об объекте и его функции.
Мы можем перестать быть пассивными наблюдателями и стать активными участниками в создании новых объектов с заданными функциональными свойствами. Это позволяет не ждать и не искать объект с заданными свойствами, а иметь его к заданному сроку в определенном месте. Но эти объекты, повторюсь, являются такой же частью природы, как и все остальные.
Понятие о функциональном объекте
В случае с функцией, лишенной объекта, мы можем попытаться построить объект, связанный с ней. Так рождается представление о функциональных объектах. Например, пусть есть преобразование электроэнергии с 110 кв в 10 кв. Это описание двух потоков — одного входящего и одного выходящего. Мы можем локализовать пространство, в котором происходит преобразование этих потоков. Это пространство назовем трансформатор напряжения. Это будет функциональный объект, потому что нам не важно, как происходит преобразование, есть ли там железяка, или преобразование происходит иным способом, но покуда есть потоки, мы знаем, что там что-то есть. И это что-то мы называем функциональным объектом. Функциональный объект ничем не отличается от физического. Просто, создавая те или иные объекты, мы концентрируем свое внимание на разных аспектах сущего.
Руководство по JavaScript, часть 4: функции
Сегодня публикуем четвёртую часть перевода руководства по JavaScript, которая посвящена функциям.
Функции в JavaScript
Поговорим о функциях в JavaScript, сделаем их общий обзор и рассмотрим подробности о них, знание которых позволит вам эффективно ими пользоваться.
Функция — это самостоятельный блок кода, который можно, один раз объявив, вызывать столько раз, сколько нужно. Функция может, хотя это и необязательно, принимать параметры. Функции возвращают единственное значение.
Кроме того, функции в JavaScript называют «функциями первого класса» так как их можно назначать переменным, их можно передавать другим функциям в качестве аргументов, их можно возвращать из других функций.
Сначала рассмотрим особенности работы с функциями и соответствующие синтаксические конструкции, которые существовали в языке до появления стандарта ES6 и актуальны до сих пор.
Вот как выглядит объявление функции (function declaration).
В наши дни такие функции называют «обычными», отличая их от «стрелочных» функций, которые появились в ES6.
Функцию можно назначить переменной или константе. Такая конструкция называется функциональным выражением (function expression).
Можно заметить, что в вышеприведённом примере функция назначена константе, но сама она имени не имеет. Такие функции называют анонимными. Подобным функциям можно назначать имена. В таком случае речь идёт об именованном функциональном выражении (named function expression).
Использование таких выражений повышает удобство отладки (в сообщениях об ошибках, где проводится трассировка стека, видно имя функции). Имя функции в функциональном выражении может понадобиться и для того, чтобы функция могла бы сама себя вызывать, без чего не обойтись при реализации рекурсивных алгоритмов.
В стандарте ES6 появились стрелочные функции (arrow function), которые особенно удобно использовать в виде так называемых «встроенных функций» (inline function) — в роли аргументов, передаваемых другим функциям (коллбэков).
Стрелочные функции, помимо того, что структуры, используемые для их объявления, получаются более компактными, чем при использовании обычных функций, отличаются от них некоторыми важными особенностями, о которых мы поговорим ниже.
Параметры функций
Параметры представляют собой переменные, которые задаются на этапе объявления функции и будут содержать передаваемые ей значения (эти значения называют аргументами). Функции в JavaScript могут либо не иметь параметров, либо иметь один или несколько параметров.
Здесь показано несколько примеров стрелочных функций.
Начиная со стандарта ES6 у функций могут быть так называемые «параметры по умолчанию» (default parameters).
Они представляют собой стандартные значения, задаваемые параметрам функций в том случае, если при её вызове значения некоторых параметров не задаются. Например, функцию, показанную выше, можно вызвать как с передачей ей всех двух принимаемых ей параметров, так и другими способами.
В ES8 появилась возможность ставить запятую после последнего аргумента функции (это называется trailing comma). Эта возможность позволяет повысить удобство редактирования кода при использовании систем контроля версий в ходе разработки программ. Подробности об этом можно почитать здесь и здесь.
Передаваемые функциям аргументы можно представлять в виде массивов. Для того чтобы разобрать эти аргументы можно воспользоваться оператором, который выглядит как три точки (это — так называемый «оператор расширения» или «оператор spread»). Вот как это выглядит.
Если функции нужно принимать много параметров, то запомнить порядок их следования может быть непросто. В таких случаях используются объекты с параметрами и возможности по деструктурированию объектов ES6.
Этот приём позволяет, описывая параметры в виде свойств объекта и передавая функции объект, получить в функции доступ к параметрам по их именам без использования дополнительных конструкций. Подробнее об этом приёме можно почитать здесь.
Значения, возвращаемые из функций
Если после ключевого слова return указать некое значение, то это значение возвращается в место вызова функции в качестве результата выполнения этой функции.
Из функции можно возвращать лишь одно значение. Для того чтобы получить возможность возврата нескольких значений, возвращать их можно либо в виде объекта, используя объектный литерал, либо в виде массива, а при вызове функции применять конструкцию деструктурирующего присваивания. Имена параметров при этом сохраняются. При этом, если нужно работать с объектом или массивом, возвращённым из функции, именно в виде объекта или массива, можно обойтись без деструктурирующего присваивания.
Конструкцию const [ name, age ] = doSomething() можно прочитать следующим образом: «объявить константы name и age и присвоить им значения элементов массива, который возвратит функция».
Вот как то же самое выглядит с использованием объекта.
Вложенные функции
Функции можно объявлять внутри других функций.
Область видимости вложенной функции ограничена внешней по отношению к ней функцией, её нельзя вызвать извне.
Методы объектов
Когда функции используются в качестве свойств объектов, такие функции называют методами объектов.
Ключевое слово this
Как видно, вызов метода start() приводит ко вполне ожидаемому результату, а вот метод stop() явно работает неправильно.
Вот как выглядит выполнение такого кода в консоли браузера.
Особенности ключевого слова this в обычных и стрелочных функциях
Всё это означает, что стрелочные функции не подходят на роль методов объектов и конструкторов (если попытаться использовать стрелочную функцию в роли конструктора — будет выдана ошибка TypeError ).
Немедленно вызываемые функциональные выражения
Немедленно вызываемое функциональное выражение (Immediately Invoked Function Expression, IIFE) — это функция, которая автоматически вызывается сразу после её объявления.
Точка с запятой перед IIFE необязательна, но её использование позволяет застраховаться от ошибок, связанных с автоматической расстановкой точек с запятой.
Поднятие функций
Если переместить вызов функции так, чтобы он шёл после её объявления, ничего не изменится.
Если же в похожей ситуации воспользоваться функциональным выражением, то похожий код выдаст ошибку.
Стрелочные функции
Сейчас мы подробнее поговорим о стрелочных функциях, с которыми мы уже встречались. Их можно считать одним из наиболее значительных новшеств стандарта ES6, они отличаются от обычных функций не только внешним видом, но и особенностями поведения. В наши дни они используются чрезвычайно широко. Пожалуй, нет ни одного современного проекта, где они не использовались бы в подавляющем большинстве случаев. Можно сказать, что их появление навсегда изменило и внешний вид JS-кода и особенности его работы.
С чисто внешней точки зрения синтаксис объявления стрелочных функций оказывается компактнее синтаксиса обычных функций. Вот объявление обычной функции.
Вот объявление стрелочной функции, которое, в целом, если не учитывать особенности стрелочных функций, аналогично предыдущему.
Как видите, параметры стрелочных функций, как и в случае с обычными функциями, описывают в скобках. При этом, если такая функция принимает всего один параметр, его можно указать без скобок. Например, вот функция, которая возвращает результат деления переданного ей числа на 2.
В результате оказывается, что стрелочные функции очень удобно использовать в ситуациях, в которых нужны маленькие функции.
▍Неявный возврат результатов работы функции
Мы уже касались этой особенности стрелочных функций, но она настолько важна, что её следует обсудить подробнее. Речь идёт о том, что однострочные стрелочные функции поддерживают неявный возврат результатов своей работы. Пример возврата примитивного значения из однострочной стрелочной функции мы уже видели. Как быть, если такая функция должна возвратить объект? В таком случае фигурные скобки объектного литерала могут запутать систему, поэтому в теле функции используются круглые скобки.
▍Ключевое слово this и стрелочные функции
Как мы уже видели, при использовании ключевого слова this в методе объекта, представленного обычной функцией, this указывает на объект, которому принадлежит метод. В таком случае говорят о привязке ключевого слова this к значению, представляющему собой контекст выполнения функции. В частности, если функция вызвана в виде метода объекта, то ключевое слово this привязано к этому объекту.
В случае же со стрелочными функциями оказывается так, что в них привязка this не выполняется, они пользуются ключевым словом this из содержащих их областей видимости. В результате их не рекомендуется использовать в качестве методов объектов.
Замыкания
Замыкания — это важная концепция в JavaScript. Фактически, если вы писали JS-функции, то вы пользовались и замыканиями. Замыкания применяются в некоторых паттернах проектирования — в том случае, если нужно организовать строгий контроль доступа к неким данным или функциям.
Когда функция вызывается, у неё есть доступ ко всему тому, что находится во внешней по отношению к ней области видимости. Но к тому, что объявлено внутри функции, извне доступа нет. То есть, если в функции была объявлена некая переменная (или другая функция), они недоступны внешнему коду ни во время выполнения функции, ни после завершения её работы. Однако если из функции возвратить другую функцию, то эта новая функция будет иметь доступ ко всему тому, что было объявлено в исходной функции. При этом всё это будет скрыто от внешнего кода в замыкании.
Рассмотрим пример. Вот функция, которая принимает имя собаки, после чего выводит его в консоль.
Значение, возвращаемое этой функцией нас пока не интересует, текст выводится в консоль с помощью IIFE, что в данном случае особой роли не играет, однако, это поможет нам увидеть связь между этой функцией и её вариантом, в котором, вместо вызова функции, которая выводит текст в консоль, мы эту функцию из переписанной функции bark() возвратим.
Результат работы код в двух случаях оказывается одинаковым. Но во втором случае то, что было передано исходной функции при её вызове (имя собаки, Roger ), хранится в замыкании, после чего используется другой функцией, возвращённой из исходной.
Проведём ещё один эксперимент — создадим, пользуясь исходной функцией, две новых, для разных собак.
Этот код выведет следующее.
Итоги
Уважаемые читатели! Как вы относитесь к стрелочным функциям в JavaScript?
Методы объекта, «this»
Объекты обычно создаются, чтобы представлять сущности реального мира, будь то пользователи, заказы и так далее:
И так же, как и в реальном мире, пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п.
Такие действия в JavaScript представлены свойствами-функциями объекта.
Примеры методов
Для начала давайте научим нашего пользователя user здороваться:
Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию для приветствия, и присвоили её свойству user.sayHi нашего объекта.
Затем мы вызвали её. Теперь пользователь может говорить!
Функцию, которая является свойством объекта, называют методом этого объекта.
Конечно, мы могли бы заранее объявить функцию и использовать её в качестве метода, примерно так:
Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированное программирование или сокращённо: «ООП».
ООП является большой предметной областью и интересной наукой само по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это – создание архитектуры, и есть хорошие книги по этой теме, такие как «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или «Объектно-ориентированный анализ и проектирование с примерами приложений» Гради Буча, а также ещё множество других книг.
Сокращённая запись метода
Существует более короткий синтаксис для методов в литерале объекта:
Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. В большинстве случаев сокращённый синтаксис предпочтителен.
Ключевое слово «this» в методах
Как правило, методу объекта необходим доступ к информации, которая хранится в объекте, чтобы выполнить с ней какие-либо действия (в соответствии с назначением метода).
Значение this – это объект «перед точкой», который использовался для вызова метода.
Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).
«this» не является фиксированным
В JavaScript ключевое слово «this» ведёт себя иначе, чем в большинстве других языков программирования. Оно может использоваться в любой функции.
В этом коде нет синтаксической ошибки:
Значение this вычисляется во время выполнения кода и зависит от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» при вызовах:
Мы даже можем вызвать функцию вовсе без использования объекта:
Если вы до этого изучали другие языки программирования, тогда вы, скорее всего, привыкли к идее «фиксированного this » – когда методы, определённые внутри объекта, всегда сохраняют в качестве значения this ссылку на свой объект (в котором был определён метод).
В JavaScript this является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а зависит от того, какой объект вызывает метод (какой объект стоит «перед точкой»).
Эта идея вычисления this в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок.
Здесь мы не будем судить о том, является ли это решение в языке хорошим или плохим. Мы должны понимать, как с этим работать, чтобы получать выгоды и избегать проблем.
Внутренняя реализация: Ссылочный тип
Этот раздел объясняет сложную тему, чтобы лучше понимать некоторые запутанные случаи.
Если вы хотите продвигаться быстрее, его можно пропустить или отложить.
Вы можете видеть, что при вызове будет ошибка, потому что значением «this» внутри функции становится undefined (полагаем, что у нас строгий режим).
Так работает (доступ к методу объекта через точку):
Так уже не работает (вызываемый метод вычисляется):
Почему? Если мы хотим понять, почему так происходит, давайте разберёмся (заглянем под капот), как работает вызов методов ( obj.method() ).
Присмотревшись поближе, в выражении obj.method() можно заметить две операции:
Итак, каким же образом информация о this передаётся из первой части во вторую?
Этот ссылочный тип (Reference Type) является внутренним типом. Мы не можем явно использовать его, но он используется внутри языка.
Результатом доступа к свойству user.hi является не функция, а значение ссылочного типа. Для user.hi в строгом режиме оно будет таким:
Когда скобки () применяются к значению ссылочного типа (происходит вызов), то они получают полную информацию об объекте и его методе, и могут поставить правильный this ( =user в данном случае, по base ).
У стрелочных функций нет «this»
Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :
Итого
Значение this определяется во время исполнения кода.
