Как функции сделать методами объекта js

Добавил пользователь Евгений Кузнецов
Обновлено: 18.09.2024

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

В JavaScript есть три категории объектов:

  • Объекты базового типа – это объекты, определённые в спецификации ECMAScript. Например, объекты типа Array , Function , Date или RegExp являются объектами базового типа.
  • Объекты среды выполнения – это объекты, определённые в среде выполнения (такой как браузер). Например, объекты типа HTMLElement , являются объектами среды выполнения.
  • Пользовательские объекты – это любой объект, созданный в результате выполнения программного кода JavaScript.

Создание объекта

Объект можно создать с помощью литерала объекта или оператора new с конструктором.

Литерал объекта – это заключённый в фигурные скобки список из нуля или более свойств (пар имя: значение), разделённых запятыми. Именем свойства может быть любой допустимый идентификатор, строковой литерал (допускается использовать пустую строку) или число. Числовые имена свойств автоматически преобразуются в строки. Значением свойства может быть значение любого типа или выражение (значением свойства в этом случае станет результат вычисления выражения):

Создание объекта с помощью оператора new:

Операции с объектом

Основные операции производимые с объектами – это добавление новых свойств, изменение уже существующих свойств, удаление свойств и обращение к свойствам.

Добавить новое свойство в объект можно присвоив свойству значение. Чтобы присвоить свойству значение, к нему нужно получить доступ. Для доступа к свойству используется один из операторов доступа: . (точка) или [] (квадратные скобки):

Обращение к свойству и изменение значения осуществляется точно так же (с помощью операторов доступа):

Удаление свойства осуществляется с помощью оператора delete:

Для перебора свойств объекта используется цикл for-in:

Методы объекта

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

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

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

Объектно-ориентированное программирование на сегодняшний день является одной из господствующих парадигм в разработке приложений, и в JavaScript мы также можем использовать все преимущества ООП. В то же время применительно к JavaScript объектно-ориентированное программирование имеет некоторые особенности.

Объекты

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

Для работы с подобными структурами в JavaScript используются объекты . Каждый объект может хранить свойства, которые описывают его состояние , и методы, которые описывают его поведение

Создание нового объекта

Есть несколько способов создания нового объекта.

Первый способ заключается в использовании конструктора Object :

В данном случае объект называется user .

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

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

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

Свойства объекта

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

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

Также можно определить свойства при определении объекта:

В этом случае для присвоения значения свойству используется символ двоеточия, а после определения свойства ставится запятая (а не точка с запятой).

Кроме того, доступен сокращенный способ определения свойств:

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

Методы объекта

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

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

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

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

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

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

Синтаксис массивов

Существует также альтернативный способ определения свойств и методов с помощью синтаксиса массивов:

Название каждого свойства или метода заключается в кавычки и в квадратные скобки, затем им также присваивается значение. Например, user["age"] = 26 .

При обращении к этим свойствам и методам можно использовать либо нотацию точки ( user.name ), либо обращаться так: user["name"]

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

Строки в качестве свойств и методов

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

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

Только в этом случае для обращении к подобным свойствам и методам мы должны использовать синтаксис массивов.

Динамическое определение имен свойств и методов

Синтаксис массивов открывает нам другую возможность - определение имени свойства вне объекта:

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

Удаление свойств

Выше мы посмотрели, как можно динамически добавлять новые свойства к объекту. Однако также мы можем удалять свойства и методы с помощью оператора delete . И как и в случае с добавлением мы можем удалять свойства двумя способами. Первый способ - использование нотации точки:

Либо использовать синтаксис массивов:

Например, удалим свойство:

После удаления свойство будет не определено, поэтому при попытке обращения к нему, программа вернет значение undefined.

Константные объекты

Возможно, нам поребуется, чтобы объект нельзя было изменить, то есть сделать константным. Однако просто определить его как обычную константу с помощью оператора const недостаточно. Например:

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

Оператор const лишь влияет на то, что мы не можем присвоить константе новое значение, например, как в следующем случае:

Тем не менее значения свойств объекта мы можем изменять.

Чтобы сделать объект действительно константным, необходимо применить специальный метод Object.freeze() . В этот метод в качестве параметра передается объект, который надо сделать константным:

Создание объекта из переменных и констант

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

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

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

То же самое относится к передаче функций методам объекта:

В данном случае объект person имеет два метода, которые соответствуют переданным в объект функциям - display() и move() . Стоит отметить, что при такой передаче функций методам объекта, мы по прежнему можем использовать в этих функциях ключевое слово this для обращения к функциональности объекта. Однако стоит быть осторожным при передаче лямбд-выражений, поскольку для глобальных лямбд-выражений this будет представлять объект окна браузера:

Фукция Object.fromEntries()

С помощью функции Object.fromEntries() можно создать объект из набора пар ключ-значение, где ключ потом будет представляет название свойства. Например, создадим объект из массивов:

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

Как мы уже знаем, в JavaScript функция – это значение.

Каждое значение в JavaScript имеет свой тип. А функция – это какой тип?

В JavaScript функции – это объекты.

Объект функции содержит несколько полезных свойств.

Что довольно забавно, логика назначения name весьма умная. Она присваивает корректное имя даже в случае, когда функция создаётся без имени и тут же присваивается, вот так:

Это работает даже в случае присваивания значения по умолчанию:

Также имена имеют и методы объекта:

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

Впрочем, на практике такое бывает редко, обычно функции имеют name .

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

Например, в коде ниже функция ask принимает в качестве параметров вопрос question и произвольное количество функций-обработчиков ответа handler .

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

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

Чтобы вызвать обработчик handler правильно, будем проверять свойство handler.length .

Это частный случай так называемого Ad-hoc-полиморфизма – обработка аргументов в зависимости от их типа или, как в нашем случае – от значения length . Эта идея имеет применение в библиотеках JavaScript.

Пользовательские свойства

Мы также можем добавить свои собственные свойства.

Давайте добавим свойство counter для отслеживания общего количества вызовов:

Свойство функции, назначенное как sayHi.counter = 0 , не объявляет локальную переменную counter внутри неё. Другими словами, свойство counter и переменная let counter – это две независимые вещи.

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

Иногда свойства функции могут использоваться вместо замыканий. Например, мы можем переписать функцию-счётчик из главы Замыкание, используя её свойство:

Свойство count теперь хранится прямо в функции, а не в её внешнем лексическом окружении.

Это хуже или лучше, чем использовать замыкание?

Основное отличие в том, что если значение count живёт во внешней переменной, то оно не доступно для внешнего кода. Изменить его могут только вложенные функции. А если оно присвоено как свойство функции, то мы можем его получить:

Поэтому выбор реализации зависит от наших целей.

Named Function Expression

Named Function Expression или NFE – это термин для Function Expression, у которого есть имя.

Например, давайте объявим Function Expression:

И присвоим ему имя:

Чего мы здесь достигли? Какова цель этого дополнительного имени func ?

Для начала заметим, что функция всё ещё задана как Function Expression. Добавление "func" после function не превращает объявление в Function Declaration, потому что оно все ещё является частью выражения присваивания.

Добавление такого имени ничего не ломает.

Функция все ещё доступна как sayHi() :

Есть две важные особенности имени func , ради которого оно даётся:

  1. Оно позволяет функции ссылаться на себя же.
  2. Оно не доступно за пределами функции.

Например, ниже функция sayHi вызывает себя с "Guest" , если не передан параметр who :

Почему мы используем func ? Почему просто не использовать sayHi для вложенного вызова?

Вообще, обычно мы можем так поступить:

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

Так происходит, потому что функция берёт sayHi из внешнего лексического окружения. Так как локальная переменная sayHi отсутствует, используется внешняя. И на момент вызова эта внешняя sayHi равна null .

Необязательное имя, которое можно вставить в Function Expression, как раз и призвано решать такого рода проблемы.

Теперь всё работает, потому что имя "func" локальное и находится внутри функции. Теперь оно взято не снаружи (и недоступно оттуда). Спецификация гарантирует, что оно всегда будет ссылаться на текущую функцию.

Итого

Функции – это объекты.

Если функция объявлена как Function Expression (вне основного потока кода) и имеет имя, тогда это называется Named Function Expression (Именованным Функциональным Выражением). Это имя может быть использовано для ссылки на себя же, для рекурсивных вызовов и т.п.

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

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

Задачи

Установка и уменьшение значения счётчика

Измените код makeCounter() так, чтобы счётчик мог увеличивать и устанавливать значение:

  • counter() должен возвращать следующее значение (как и раньше).
  • counter.set(value) должен устанавливать счётчику значение value .
  • counter.decrease() должен уменьшать значение счётчика на 1.

Посмотрите код из песочницы с полным примером использования.

P.S. Для того, чтобы сохранить текущее значение счётчика, можно воспользоваться как замыканием, так и свойством функции. Или сделать два варианта решения: и так, и так.

В решении использована локальная переменная count , а методы сложения записаны прямо в counter . Они разделяют одно и то же лексическое окружение и также имеют доступ к текущей переменной count .

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

  1. Простой вызов функции — hello('Привет')
  2. Вызов метода объекта — user.hello('Привет')
  3. Вызов с указанием контекста — hello.call(user, 'Привет')
  4. Вызов конструктора — new User('Вася')

1. Простой вызов функции

Здесь все просто — контектом вызова будет глобальный объект window или undefined , в зависимости от режима strict .

2. Вызов метода объекта

Метод — это функция, хранящаяся в объекте. При вызове метода this — это объект, которому принадлежит метод.

Ловушка — потеря контекста

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

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

Метод setTimeout() в браузере имеет особенность — он устанавливает this=window для вызова функции. Таким образом, для this.surname он пытается получить window.surname , которого не существует. В других подобных случаях this обычно просто становится undefined .

Самый простой способ обойти ловушку — это обернуть вызов в анонимную функцию, создав замыкание:

Теперь объект user достаётся из замыкания, а затем вызывается его метод show() .

3. Вызов с указанием контекста

Методы call() и apply() объекта Function позволяют явно указать this .

Вызов функции при помощи func.apply работает аналогично func.call , но принимает массив аргументов вместо списка.

4. Вызов конструктора

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  • Имя функции-конструктора должно начинаться с большой буквы
  • Функция-конструктор должна вызываться при помощи оператора new

Когда функция вызывается как new User(…) , происходит следующее:

  • Создаётся новый пустой объект, и он присваивается this
  • Выполняется код функции. Обычно он модифицирует this , добавляя туда новые свойства
  • Возвращается значение this

Другими словами, вызов new User(…) делает примерно вот что:

Вместо заключения

Мы уже видели пример потери this . Как только метод передаётся отдельно от объекта — this теряется.

В современном JavaScript у функций есть встроенный метод bind , который позволяет зафиксировать this .

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Читайте также: