Объекты обычно создаются для представления сущностей реального мира, таких как пользователи, заказы и т. д.:
пусть пользователь = { имя: «Джон», возраст: 30 };
А в реальном мире пользователь может действовать : выбрать что-то из корзины, войти в систему, выйти из системы и т. д.
Действия представлены в JavaScript функциями в свойствах.
Для начала научим user
здороваться:
пусть пользователь = { имя: «Джон», возраст: 30 }; user.sayHi = функция() { Предупреждение("Привет!"); }; пользователь.sayHi(); // Привет!
Здесь мы только что использовали функциональное выражение, чтобы создать функцию и присвоить ее свойству user.sayHi
объекта.
Тогда мы можем назвать его user.sayHi()
. Теперь пользователь может говорить!
Функция, являющаяся свойством объекта, называется его методом .
Итак, у нас есть sayHi
объекта user
.
Конечно, мы могли бы использовать в качестве метода заранее объявленную функцию, например:
пусть пользователь = { // ... }; // сначала объявляем функция SayHi() { Предупреждение("Привет!"); } // затем добавляем как метод user.sayHi = сказатьПривет; пользователь.sayHi(); // Привет!
Объектно-ориентированное программирование
Когда мы пишем наш код, используя объекты для представления сущностей, это называется объектно-ориентированным программированием, короче: «ООП».
ООП — это большое дело, интересная наука сама по себе. Как правильно выбрать сущности? Как организовать взаимодействие между ними? Это архитектура, и на эту тему есть замечательные книги, такие как «Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования» Э. Гаммы, Р. Хелма, Р. Джонсона, Дж. Виссайдса или «Объектно-ориентированный анализ и проектирование с использованием Приложения» Г. Буча и др.
Существует более короткий синтаксис для методов в литерале объекта:
// эти объекты делают то же самое пользователь = { сказатьПривет: функция() { Оповещение("Привет"); } }; // Сокращение метода выглядит лучше, не так ли? пользователь = { sayHi() { // то же, что "sayHi: function(){...}" Оповещение("Привет"); } };
Как показано, мы можем опустить "function"
и просто написать sayHi()
.
Честно говоря, обозначения не полностью идентичны. Существуют тонкие различия, связанные с наследованием объектов (которые будут рассмотрены позже), но на данный момент они не имеют значения. Почти во всех случаях предпочтителен более короткий синтаксис.
Обычно для выполнения своей работы методу объекта требуется доступ к информации, хранящейся в объекте.
Например, коду внутри user.sayHi()
может потребоваться имя user
.
Для доступа к объекту метод может использовать ключевое слово this
.
Значением this
является объект «до точки», тот, который использовался для вызова метода.
Например:
пусть пользователь = { имя: «Джон», возраст: 30, сказатьПривет() { // «это» — «текущий объект» оповещение(это.имя); } }; пользователь.sayHi(); // Джон
Здесь во время выполнения user.sayHi()
значением this
будет user
.
Технически доступ к объекту возможен и без this
, ссылаясь на него через внешнюю переменную:
пусть пользователь = { имя: «Джон», возраст: 30, сказатьПривет() { оповещение(имя_пользователя); // «пользователь» вместо «это» } };
…Но такой код ненадежен. Если мы решим скопировать user
в другую переменную, например admin = user
и перезаписать user
чем-то другим, то он получит доступ не к тому объекту.
Это показано ниже:
пусть пользователь = { имя: «Джон», возраст: 30, сказатьПривет() { оповещение(имя_пользователя); // приводит к ошибке } }; пусть администратор = пользователь; пользователь = ноль; // перезаписываем, чтобы все было очевидно администратор.sayHi(); // Ошибка типа: невозможно прочитать свойство «имя» со значением null
Если бы мы использовали this.name
вместо user.name
внутри alert
, код работал бы.
В JavaScript ключевое слово this
ведет себя в отличие от большинства других языков программирования. Его можно использовать в любой функции, даже если это не метод объекта.
В следующем примере нет синтаксической ошибки:
функция SayHi() { предупреждение(это.имя); }
Значение this
оценивается во время выполнения в зависимости от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет в вызовах разное «this»:
let user = { name: "Джон" }; пусть администратор = {имя: "Администратор"}; функция SayHi() { предупреждение(это.имя); } // используем одну и ту же функцию в двух объектах user.f = сказатьПривет; admin.f = сказатьПривет; // эти вызовы имеют разные значения this // «это» внутри функции — это объект «до точки» пользователь.f(); // Джон (это == пользователь) админ.ф(); // Администратор (это == администратор) администратор['f'](); // Администратор (точка или квадратные скобки дают доступ к методу – не имеет значения)
Правило простое: если вызывается obj.f()
, то this
obj
во время вызова f
. Итак, в приведенном выше примере это либо user
, либо admin
.
Вызов без объекта: this == undefined
Мы можем даже вызвать функцию вообще без объекта:
функция SayHi() { предупреждение (это); } сказатьПривет(); // неопределенный
В данном случае this
значение undefined
в строгом режиме. Если мы попытаемся получить доступ к this.name
, произойдет ошибка.
В нестрогом режиме значением this
в таком случае будет глобальный объект ( window
в браузере, мы вернемся к нему позже в главе «Глобальный объект»). Это историческое поведение, при котором "use strict"
исправления.
Обычно такой вызов является ошибкой программирования. Если this
есть внутри функции, она ожидает вызова в контексте объекта.
Последствия отмены this
привязки
Если вы знакомы с другим языком программирования, то вы, вероятно, привыкли к идее «привязки this
», когда методы, определенные в объекте, всегда имеют this
ссылающийся на этот объект.
В JavaScript this
«бесплатно», его значение оценивается во время вызова и зависит не от того, где был объявлен метод, а от того, какой объект находится «перед точкой».
Концепция оценки времени выполнения this
как плюсы, так и минусы. С одной стороны, функцию можно повторно использовать для разных объектов. С другой стороны, большая гибкость создает больше возможностей для ошибок.
Здесь наша позиция не в том, чтобы судить, хорошее или плохое это решение по языковому дизайну. Разберемся, как с этим работать, как получить выгоду и избежать проблем.
Стрелочные функции особенные: у них нет «своего» this
. Если мы ссылаемся на this
из такой функции, оно берется из внешней «нормальной» функции.
Например, здесь arrow()
использует this
метод user.sayHi()
:
пусть пользователь = { Имя: "Илья", сказатьПривет() { пусть стрелка = () => alert(this.firstName); стрелка(); } }; пользователь.sayHi(); // Илья
Это особенность стрелочных функций, она полезна, когда мы на самом деле не хотим иметь отдельный this
, а хотим взять его из внешнего контекста. Позже в главе «Возвращаясь к стрелочным функциям» мы более подробно рассмотрим стрелочные функции.
Функции, хранящиеся в свойствах объекта, называются «методами».
Методы позволяют объектам «действовать» подобно object.doSomething()
.
Методы могут ссылаться на объект this
образом.
Значение this
параметра определяется во время выполнения.
Когда функция объявлена, она может использовать this
, но this
не имеет значения до тех пор, пока функция не будет вызвана.
Функцию можно копировать между объектами.
Когда функция вызывается с синтаксисом «метода»: object.method()
, значением this
во время вызова является object
.
Обратите внимание, что стрелочные функции особенные: у них нет this
. Когда доступ this
осуществляется внутри стрелочной функции, он берется извне.
важность: 5
Здесь функция makeUser
возвращает объект.
Каков результат доступа к его ref
? Почему?
функция makeUser() { возвращаться { имя: «Джон», ссылка: это }; } пусть пользователь = makeUser(); предупреждение(пользователь.ref.имя); // Каков результат?
Ответ: ошибка.
Попробуйте:
функция makeUser() { возвращаться { имя: «Джон», ссылка: это }; } пусть пользователь = makeUser(); предупреждение(пользователь.ref.имя); // Ошибка: невозможно прочитать имя свойства неопределенного значения
Это потому, что правила, устанавливающие this
не учитывают определение объекта. Имеет значение только момент звонка.
Здесь значение this
внутри makeUser()
undefined
, поскольку оно вызывается как функция, а не как метод с «точечным» синтаксисом.
Значение this
параметра одно для всей функции, блоки кода и объектные литералы на него не влияют.
Итак, ref: this
принимает текущую this
.
Мы можем переписать функцию и вернуть то же this
с undefined
значением:
функция makeUser(){ верните это; // на этот раз объектного литерала нет } Предупреждение( makeUser().name ); // Ошибка: невозможно прочитать имя свойства неопределенного значения
Как вы можете видеть, результат alert( makeUser().name )
такой же, как результат alert( user.ref.name )
из предыдущего примера.
Вот противоположный случай:
функция makeUser() { возвращаться { имя: «Джон», ссылка() { верните это; } }; } пусть пользователь = makeUser(); предупреждение(user.ref().name); // Джон
Теперь это работает, потому что user.ref()
— это метод. И значение this
устанавливается для объекта перед точкой .
.
важность: 5
Создайте объектный calculator
тремя методами:
read()
запрашивает два значения и сохраняет их как свойства объекта с именами a
и b
соответственно.
sum()
возвращает сумму сохраненных значений.
mul()
умножает сохраненные значения и возвращает результат.
пусть калькулятор = { // ... ваш код... }; калькулятор.читать(); предупреждение(калькулятор.сумма()); предупреждение(калькулятор.mul());
Запустить демо-версию
Откройте песочницу с тестами.
пусть калькулятор = { сумма () { вернуть this.a + this.b; }, мул() { верните this.a * this.b; }, читать() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; калькулятор.читать(); предупреждение(калькулятор.сумма()); предупреждение(калькулятор.mul());
Откройте решение с тестами в песочнице.
важность: 2
Есть объект ladder
, которая позволяет вам подниматься и спускаться:
пусть лестница = { шаг: 0, вверх() { этот.шаг++; }, вниз() { этот.шаг--; }, showStep: function() { // показывает текущий шаг предупреждение(этот.шаг); } };
Теперь, если нам нужно сделать несколько вызовов последовательно, мы можем сделать это так:
лестница.вверх(); лестница.вверх(); лестница.вниз(); лестница.showStep(); // 1 лестница.вниз(); лестница.showStep(); // 0
Измените код up
, down
и showStep
, чтобы сделать вызовы цепочками, например:
лестница.вверх().вверх().вниз().showStep().вниз().showStep(); // показывает 1, затем 0
Такой подход широко используется в библиотеках JavaScript.
Откройте песочницу с тестами.
Решение состоит в том, чтобы возвращать сам объект при каждом вызове.
пусть лестница = { шаг: 0, вверх() { этот.шаг++; верните это; }, вниз() { этот.шаг--; верните это; }, шоуШтеп() { предупреждение(этот.шаг); верните это; } }; лестница.вверх().вверх().вниз().showStep().вниз().showStep(); // показывает 1, затем 0
Мы также можем написать один вызов в каждой строке. Для длинных цепочек это более читабельно:
лестница .вверх() .вверх() .вниз() .showStep() // 1 .вниз() .showStep(); // 0
Откройте решение с тестами в песочнице.