Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Для будущих студентов курса "JavaScript Developer. Basic" и всех желающих подготовили перевод полезной статьи.
Приглашаем также посмотреть открытый урок на тему "Прототипное наследование в JavaScript".
В первой части мы рассмотрели четыре различных разновидности Объектно-ориентированного программирования. Два из них — Классы и Фабричные функции — проще в использовании по сравнению с остальными.
Но вопросы остаются: Какую из них использовать? И почему?
Чтобы продолжить обсуждение функций Классов и Фабричных функций, необходимо понять три понятия, которые тесно связаны с Объектно-ориентированным программированием:
Наследование
Инкапсуляция
this
Мы только что говорили о наследстве. Теперь давайте поговорим об инкапсуляции.
Инкапсуляция
Инкапсуляция — это большое слово, но оно имеет простое значение. Инкапсуляция — это акт помещения одной вещи внутрь другой, чтобы вещь внутри не просочилась наружу. Подумайте о том, чтобы хранить воду в бутылке. Бутылка предотвращает утечку воды.
В JavaScript мы заинтересованы в том, чтобы ограничить область видимости (которые могут включать в себя функции), чтобы эти переменные не просочились во внешнюю область видимости. Это означает, что для понимания инкапсуляции необходимо понимать границы видимости. Мы рассмотрим объяснение, но вы также можете использовать эту статью, чтобы подкрепить свои знания о диапазонах.
Простая инкапсуляция
Самая простая форма инкапсуляции — блочная сфера применения.
{
// Variables declared here won't leak out
}
Когда вы находитесь в блоке, вы можете получить доступ к переменным, которые декларируются вне блока.
const food = 'Hamburger'
{
console.log(food)
}
Замечание: Переменные декларированные с var
соблюдают область видимого блока. Поэтому я рекомендую использовать let or const для декларирования переменных.
Инкапсулирование с функциями
Функции ведут себя как блок области. Когда вы декларируете переменную внутри функции, они не могут утечь из этой функции. Это работает для всех переменных, даже для тех, которые декларированы с помощью функции.
function sayFood () {
const food = 'Hamburger'
}
sayFood()
console.log(food)
Аналогично, когда вы находитесь внутри функции, вы можете получить доступ к переменным, которые декларируются вне этой функции.
const food = 'Hamburger'
function sayFood () {
console.log(food)
}
sayFood()
Функции могут возвращать значение. Это возвращаемое значение может быть использовано позже, вне функции.
function sayFood () {
return 'Hamburger'
}
console.log(sayFood())
Функции-замыкания
Функции-замыкания — это усовершенствованная форма инкапсуляции. Это просто функции, завернутые в функции.
// Here's a closure
function outsideFunction () {
function insideFunction () { /* ...*/ }
}
Переменные декларированные в outsideFunction
могут быть использованы в insideFunction
.
function outsideFunction () {
const food = 'Hamburger'
console.log('Called outside')
return function insideFunction () {
console.log('Called inside')
console.log(food)
}
}
// Calls `outsideFunction`, which returns `insideFunction`
// Stores `insideFunction` as variable `fn`
const fn = outsideFunction()
// Calls `insideFunction`
fn()
Инкапсулирование и объектно-ориентированное программирование
Когда вы строите объекты, вы хотите сделать некоторые свойства общедоступными (чтобы люди могли их использовать). Но вы также хотите сохранить некоторые свойства приватными (чтобы другие не могли нарушить Вашу реализацию).
Давайте проработаем это на примере, чтобы сделать вещи более понятными. Допустим, у нас есть a Car
blueprint. Когда мы производим новые автомобили, мы заправляем каждый автомобиль 50 литрами топлива.
class Car {
constructor () {
this.fuel = 50
}
}
Здесь мы раскрыли fuel
свойство. Пользователи могут использовать fuel
, чтобы получить количество топлива, оставшегося в их машинах.
const car = new Car()
console.log(car.fuel) // 50
Пользователи также могут использовать fuel
свойство для установки любого количества топлива.
const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000
Добавим условие и скажем, что каждый автомобиль имеет максимальную вместимость 100 литров. При таком условии мы не хотим, чтобы пользователи свободно устанавливали свойство топлива, потому что они могут сломать автомобиль.
Есть два способа запретить пользователям устанавливать fuel
свойство:
Приватные по соглашению
Действительные приватные члены класса
Приватные по соглашению
В JavaScript существует практика добавления подчеркивания. Это означает, что переменная является приватной и не должна использоваться.
class Car {
constructor () {
// Denotes that `_fuel` is private. Don't use it!
this._fuel = 50
}
}
Мы часто создаем методы, чтобы получить и установить переменную fuel
"приватной”.
class Car {
constructor () {
// Denotes that `_fuel` is private. Don't use it!
this._fuel = 50
}
getFuel () {
return this._fuel
}
setFuel (value) {
this._fuel = value
// Caps fuel at 100 liters
if (value > 100) this._fuel = 100
}
}
Пользователи должны использовать методы getFuel
и setFuel
для получения и установки fuel
.
const car = new Car()
console.log(car.getFuel()) // 50
car.setFuel(3000)
console.log(car.getFuel()) // 100
Но fuel
на самом деле не приватный. Это все еще открытая переменная. Вы все еще можете получить к ней доступ, вы все еще можете использовать ее, и вы все еще можете злоупотреблять ею (даже если злоупотребление является случайностью).
const car = new Car()
console.log(car.getFuel()) // 50
car._fuel = 3000
console.log(car.getFuel()) // 3000
Нам нужно использовать действительные приватные переменные, если мы хотим полностью запретить пользователям доступ к ним.
Действительные приватные члены класса
Здесь участники ссылаются на переменные, функции и методы. Это коллективный термин.
Приватные члены с классами
Классы позволяют создавать приватные члены класса, добавляя #
к переменной.
class Car {
constructor () {
this.#fuel = 50
}
}
К сожалению, вы не можете использовать #
напрямую в функции constructor
Сначала необходимо декларировать приватную переменную вне конструктора.
class Car {
// Declares private variable
#fuel
constructor () {
// Use private variable
this.#fuel = 50
}
}
В этом случае мы можем использовать сокращение и объявить #fuel
заранее, так как мы установили fuel на 50.
class Car {
#fuel = 50
}
Вы не можете получить доступ к #fuel
из Car
. У вас будет ошибка.
const car = new Car()
console.log(car.#fuel)
Вам понадобятся методы (такие как getFuel
или setFuel
) чтобы использовать переменную #fuel
.
class Car {
#fuel = 50
getFuel () {
return this.#fuel
}
setFuel (value) {
this.#fuel = value
if (value > 100) this.#fuel = 100
}
}
const car = new Car()
console.log(car.getFuel()) // 50
car.setFuel(3000)
console.log(car.getFuel()) // 100
Примечание: Я предпочитаю
Getters
andSetters
вместоgetFuel
иsetFuel
. Синтакс легче читать.
class Car {
#fuel = 50
get fuel () {
return this.#fuel
}
set fuel (value) {
this.#fuel = value
if (value > 100) this.#fuel = 100
}
}
const car = new Car()
console.log(car.fuel) // 50
car.fuel = 3000
console.log(car.fuel) // 100
Приватные члены класса с фабричными функциями
Фабричные функции создают Приватных членов автоматически. Вам просто нужно декларировать переменную, как обычно. Пользователи не смогут получить эту переменную нигде больше. Причина в том, что переменные находятся в функции и, следовательно, инкапсулируются по умолчанию.
function Car () {
const fuel = 50
}
const car = new Car()
console.log(car.fuel) // undefined
console.log(fuel) // Error: `fuel` is not defined
Мы можем создать функции getter
and setter
чтобы использовать приватную переменную fuel
.
function Car () {
const fuel = 50
return {
get fuel () {
return fuel
},
set fuel (value) {
fuel = value
if (value > 100) fuel = 100
}
}
}
const car = new Car()
console.log(car.fuel) // 50
car.fuel = 3000
console.log(car.fuel) // 100
Вот так! Просто и легко!
Вердикт об инкапсуляции
Инкапсуляция с фабричными функциями проще и понятнее. Они опираются на сферы применения, которые составляют большую часть языка JavaScript.
Инкапсуляция с классами, с другой стороны, требует добавления #
к приватной переменной. Это может сделать вещи неуклюжими.
Мы рассмотрим финальную концепцию — это для завершения сравнения между Классами и Фабричными функциями — в следующем разделе.
Классы в сравнении с Фабричными функциями — this переменная
this
— один из основных аргументов против использования классов для объектно-ориентированного программирования. Почему? Потому что this
значение изменяется в зависимости от того, как оно используется. Это может сбить с толку многих разработчиков (как новых, так и опытных).
Но концепция this
относительно проста в реальности. Существует только шесть контекстов, в которых вы можете использовать this
. Если вы освоите эти шесть контекстов, у вас не будет проблем с использованием this
.
Шесть контекстов:
В глобальном контексте
В создании объекта
В свойстве объекта / метода
В простой функции
В стрелочной функции
В слушателе событий
Я затронул эти 6 контекстов в деталях. Прочитайте, если вам нужно понять this
.
Замечание: Не стесняйтесь изучить this. Это важная концепция, которую вам необходимо понять, если вы намерены овладеть JavaScript.
Возвращайся к этой статье после того, как закрепишь свои знания об this
. Мы более подробно обсудим использование this
в классах и фабричных функциях.
Уже вернулись? Хорошо. Поехали!
Использование this в классах
This
в случае использования в классе. (Он использует контекст "В свойстве/методе объекта".) Вот почему свойства и методы можно устанавливать на экземпляре внутри функции constructor
.
class Human {
constructor (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
console.log(this)
}
}
const chris = new Human('Chris', 'Coyier')
Использование This в функции Конструктора
Если вы используете this
в функции и new
чтобы создать экземпляр, this
будет ссылаться на экземпляр. Так создается функция Конструктора.
function Human (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
console.log(this)
}
const chris = new Human('Chris', 'Coyier')
Я упомянул о функциях конструктора, потому что вы можете использовать this
внутри фабричных функций. Но this
указывает на Window (или undefined
если вы используете ES6 Модули, или пакеты такие как webpack).
// NOT a Constructor function because we did not create instances with the `new` keyword
function Human (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
console.log(this)
}
const chris = Human('Chris', 'Coyier')
По существу, когда вы создаете фабричную функцию, вы не должны использовать this
как будто это функция Конструктора. Это одна маленькая заминка, которую люди испытывают с this
. Я хотел осветить проблему и прояснить ее.
Использование This в фабричной функции
Правильным способом использования this
в фабричные функции является его использование в контексте "свойство/метод объекта".
function Human (firstName, lastName) {
return {
firstName,
lastName,
sayThis () {
console.log(this)
}
}
}
const chris = Human('Chris', 'Coyier')
chris.sayThis()
Несмотря на то, что вы можете использовать this
в фабричных функциях, вам не нужно их использовать. Вы можете создать переменную, указывающую на экземпляр. Как только вы это сделаете, вы можете использовать эту переменную вместо this
. Вот пример в работе.
function Human (firstName, lastName) {
const human = {
firstName,
lastName,
sayHello() {
console.log(`Hi, I'm ${human.firstName}`)
}
}
return human
}
const chris = Human('Chris', 'Coyier')
chris.sayHello()
human.firstName
ясней чем this.firstName
потому что human
бесконечно указывает на экземпляр. Ты знаешь, когда видишь код.
Если вы привыкли к JavaScript, вы также можете заметить, что нет необходимости даже писать human.firstName
изначально! Достаточно только firstName
потому что firstName
находится в лексическом диапазоне. (Прочитайте эту статью если вам необходима помощь с областью применения.)
function Human (firstName, lastName) {
const human = {
firstName,
lastName,
sayHello() {
console.log(`Hi, I'm ${firstName}`)
}
}
return human
}
const chris = Human('Chris', 'Coyier')
chris.sayHello()
То, что мы пока охватили, очень просто. Нелегко решить, действительно ли this
необходимо, пока мы не создадим достаточно сложный пример. Так что давайте сделаем это.
Детальный пример
Вот и схема. Допустим, у нас есть Human blueprint
. У Human
есть свойства firstName
и lastName
, и метод sayHello
.
У нас есть Developer blueprint
, который выходит из Human
. Разработчики могут программировать, так что у них будет метод code
. Разработчики также хотят декларировать, что они разработчики, так что нам нужно перезаписать sayHello
и добавить I'm a Developer
к консоли.
Мы создадим этот пример с Классами и Фабричными функциями. (Приведем пример с this
и пример без this
для фабричных функций).
Пример с Классами
Для начала у на есть Human blueprint
. У Human
есть свойства firstName
и lastName
, а также метод sayHello
.
class Human {
constructor (firstName, lastName) {
this.firstName = firstName
this.lastname = lastName
}
sayHello () {
console.log(`Hello, I'm ${this.firstName}`)
}
}
У нас есть Developer blueprint
который выходит из Human
. Разработчики могут кодить, так что у них будет метод code
.
class Developer extends Human {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
}
}
Разработчики также хотят заявить, что они разработчики. Нам нужно переписать sayHello
и добавить I'm a Developer
в консоль. Мы это делаем вызывая метод Human‘s sayHello
. Мы делаем это используя super
.
class Developer extends Human {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
}
sayHello () {
super.sayHello()
console.log(`I'm a developer`)
}
}
Пример фабричных функций (с this)
Также, во-первых, у нас есть Human blueprint
. У Human
есть свойства firstName
и lastName
, а также метод sayHello
.
function Human () {
return {
firstName,
lastName,
sayHello () {
console.log(`Hello, I'm ${this.firstName}`)
}
}
}
Следующее, у нас есть Developer blueprint
который выходит из Human
. Разработчики могут программировать, так что у них будут метод code
.
function Developer (firstName, lastName) {
const human = Human(firstName, lastName)
return Object.assign({}, human, {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
}
})
}
Разработчики также хотят заявить, что они разработчики. Нам нужно переписать sayHello
и добавить I'm a Developer
в консоль. Мы это делаем вызывая метод Human‘s sayHello
. Мы делаем это используя экземпляр human
.
function Developer (firstName, lastName) {
const human = Human(firstName, lastName)
return Object.assign({}, human, {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
},
sayHello () {
human.sayHello()
console.log('I\'m a developer')
}
})
}
Пример фабричных функций (без this)
Вот полный код, использующий фабричные функции (с this
):
function Human (firstName, lastName) {
return {
firstName,
lastName,
sayHello () {
console.log(`Hello, I'm ${this.firstName}`)
}
}
}
function Developer (firstName, lastName) {
const human = Human(firstName, lastName)
return Object.assign({}, human, {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
},
sayHello () {
human.sayHello()
console.log('I\'m a developer')
}
})
}
Вы заметили, что firstName
доступен в области лексики у обоих Human
и Developer
? Это значит, что мы можем пропустить this
и использовать firstName
напрямую в обоих blueprints
function Human (firstName, lastName) {
return {
// ...
sayHello () {
console.log(`Hello, I'm ${firstName}`)
}
}
}
function Developer (firstName, lastName) {
// ...
return Object.assign({}, human, {
code (thing) {
console.log(`${firstName} coded ${thing}`)
},
sayHello () { /* ... */ }
})
}
Видите? Это означает, что вы можете безопасно пропустить this
из своего кода при использовании фабричных функций.
Вердикт для this
Проще говоря, Классы требуют this
, а Фабричные функции — нет. Я предпочитаю фабричные функции здесь, потому что:
• Контекст this может измениться (что может сбить с толку).
• Код, написанный с помощью фабричных функций, короче и чище (так как мы можем использовать инкапсулированные переменные без написания this.#variable
).
Далее следует последний раздел, где мы строим простой компонент вместе с Классами и фабричными функциями. Вы увидите, чем они отличаются и как использовать слушателей событий с каждой особенностью
Классы против Фабричных функций — Слушатели событий
Большинство статей по объектно-ориентированному программированию показывают примеры без слушателей событий. Эти примеры может быть проще понять, но они не отражают работу, которую мы выполняем как фронтенд-разработчики. Работа, которую мы выполняем, требует наличия слушателей событий — по простой причине — потому что нам нужно строить вещи, которые полагаются на пользовательский ввод.
Так как слушатели событий меняют контекст this
, они могут сделать классы неудобными в работе. В то же время они делают фабричные функции более привлекательными.
Но на самом деле это не так.
Изменение в this
не имеет значения, если вы знаете, как справиться с this
как в Классах, так и в Фабричных функциях. Мало статей на эту тему, так что я подумал, что было бы неплохо дополнить эту статью простым компонентом, использующим разнообразие по объектно-ориентированному программированию.
Построение счетчика
В этой статье мы построим простой счетчик. Мы будем использовать все, что вы узнали в этой статье — включая приватные переменные.
Допустим, счетчик содержит две вещи:
Сам счетчик
Кнопку для увеличения количества
Вот самый простой HTML для счетчика:
<div class="counter">
<p>Count: <span>0</span>
<button>Increase Count</button>
</div>
Построение счетчика с Классами
Для простоты мы попросим пользователей найти и передать HTML счетчика в класс Counter
.
class Counter () {
constructor (counter) {
// Do stuff
}
}
// Usage
const counter = new Counter(document.querySelector('.counter'))
Нам нужно получить два элемента в классе Счетчик:
<span>
, который содержит счетчик — нам нужно обновить этот элемент, когда счетчик увеличится.Кнопка
<button
> — нам нужно добавить слушателя событий в этот класс элементов.
Counter () {
constructor (counter) {
this.countElement = counter.querySelector('span')
this.buttonElement = counter.querySelector('button')
}
}
Мы инициализируем count
и устанавливаем ее в то, что показывает элемент countElement
. Мы будем использовать приватную #count
, так как счетчик не должен быть выставлен в другом месте.
class Counter () {
#count
constructor (counter) {
// ...
this.#count = parseInt(countElement.textContent)
}
}
Когда пользователь нажимает кнопку <button>
, мы хотим увеличить #count
. Мы можем сделать это с помощью другого метода. Мы назовем этот метод increaseCount
.
class Counter () {
#count
constructor (counter) { /* ... */ }
increaseCount () {
this.#count = this.#count + 1
}
}
Далее, нам нужно обновить DOM новым #count
. Для этого создадим метод под названием updateCount
. Мы будем называть updateCount
из increaseCount
class Counter () {
#count
constructor (counter) { /* ... */ }
increaseCount () {
this.#count = this.#count + 1
this.updateCount()
}
updateCount () {
this.countElement.textContent = this.#count
}
}
Теперь мы готовы добавить слушателя событий.
Добавление слушателя событий
Мы добавим слушателя событий в this.buttonElement
. К сожалению, мы не можем сразу использовать increaseCount
в качестве обратного вызова. Вы получите ошибку, если попробуете.
class Counter () {
// ...
constructor (counter) {
// ...
this.buttonElement.addEventListener('click', this.increaseCount)
}
// Methods
}
Вы получаете ошибку, потому что this
указывает на buttonElement
. (Это контекст слушателя событий.) Вы увидите buttonElement
, если войдете в консоль.
Нам нужно изменить значение this
обратно в экземпляр для increaseCount
, чтобы все заработало. Есть два способа сделать это:
Использовать
bind
Использовать стрелочные функции
Большинство людей используют первый метод (но второй проще).
Добавление слушателя событий с bind
Bind
возвращает новую функцию. Она позволяет изменить this
на первый переданный аргумент. Обычно люди создают слушателей событий, вызывая bind(this)
.
class Counter () {
// ...
constructor (counter) {
// ...
this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
}
// ...
}
Это работает, но не очень приятно читать. Он также неудобен для начинающих, потому что bind
рассматривается как продвинутая функция JavaScript.
Стрелочные функции
Второй способ — использование стрелочных функций. Стрелочные функции работают, так как они сохраняют значение this
в лексическом контексте.
Большинство людей пишут методы внутри обратного вызова стрелочных функций, как это:
class Counter () {
// ...
constructor (counter) {
// ...
this.buttonElement.addEventListener('click', _ => {
this.increaseCount()
})
}
// Methods
}
Это работает, но это долгий путь. Вообще-то, есть короткий путь.
Вы можете создать increaseCount
с помощью функций со стрелками. Если вы сделаете это, то это значение для increaseCount
будет привязано к значению экземпляра сразу.
Итак, вот код, который вам нужен:
class Counter () {
// ...
constructor (counter) {
// ...
this.buttonElement.addEventListener('click', this.increaseCount)
}
increaseCount = () => {
this.#count = this.#count + 1
this.updateCounter()
}
// ...
}
Код
Вот полная версия кода на основе классов (с использованием стрелочных функций).
HTML
<div class="counter">
<p>Count: <span>0</span></p>
<button>Increase Count</button>
</div>
CSS
html {
font: 1em sans-serif;
}
body {
padding: 1em;
}
p {
font-size: 2em;
line-height: 1;
margin: 1rem 0;
}
button {
font: inherit;
padding: 0.5em 0.75em;
background-color: #eee;
border: 1px solid #222;
color: black;
}
Babel
class Counter {
#count = 0
constructor (counter) {
this.countElement = counter.querySelector('span')
this.buttonElement = counter.querySelector('button')
// Initializes count
this.#count = parseInt(this.countElement.textContent)
// Adds event listener
this.buttonElement.addEventListener('click', this.increaseCount)
}
updateCounter () {
this.countElement.textContent = this.#count
}
increaseCount = () => {
this.#count = this.#count + 1
this.updateCounter()
}
}
const counter = new Counter(document.querySelector('.counter'))
Результат:
Создание счетчика с фабричными функциями
Мы сделаем то же самое здесь. Мы заставим пользователей передать HTML счетчика на фабричный Counter
.
function Counter (counter) {
// ...
}
const counter = Counter(document.querySelector('.counter'))
Нам нужно получить два элемента из counter
— <span>
и <button>
. Здесь мы можем использовать обычные переменные (без this
), потому что они уже являются приватными. Мы не будем их раскрывать.
function Counter (counter) {
const countElement = counter.querySelector('span')
const buttonElement = counter.querySelector('button')
}
Мы инициализируем переменную счетчика значением, присутствующим в HTML.
function Counter (counter) {
const countElement = counter.querySelector('span')
const buttonElement = counter.querySelector('button')
let count = parseInt(countElement.textContext)
}
Мы увеличим переменную count
методом increaseCount
. Здесь можно выбрать обычную функцию, но мне нравится создавать метод, чтобы все было аккуратно и аккуратно.
function Counter (counter) {
// ...
const counter = {
increaseCount () {
count = count + 1
}
}
}
Наконец, мы обновим счет методом updateCount
. Мы также вызовем updateCount
из метода increaseCount
.
function Counter (counter) {
// ...
const counter = {
increaseCount () {
count = count + 1
counter.updateCount()
}
updateCount () {
increaseCount()
}
}
}
Обратите внимание, что я использовал counter.updateCount
вместо this.updateCount
? Мне это нравится, потому что counter
более понятен по сравнению с this
. Я также делаю это, потому что новички могут также сделать ошибку с this
внутри фабричных функций (о которых я расскажу позже).
Добавление слушателей событий
Мы можем добавить слушателей событий в buttonElement
. Когда мы это сделаем, то сразу же сможем использовать counter.increaseCount
в качестве обратного вызова.
Мы можем сделать это, потому что мы не использовали this
, так что это не имеет значения, даже если слушатели события изменят значение this
.
function Counter (counterElement) {
// Variables
// Methods
const counter = { /* ... */ }
// Event Listeners
buttonElement.addEventListener('click', counter.increaseCount)
}
Понимание this
this
можно использовать в фабричных функциях. Но вы должны использовать this
в контексте метода.
В следующем примере, если вы вызываете counter.increaseCount
, JavaScript также вызовет counter.updateCount
. this
работает, так как указывает на переменную counter
.
function Counter (counterElement) {
// Variables
// Methods
const counter = {
increaseCount() {
count = count + 1
this.updateCount()
}
}
// Event Listeners
buttonElement.addEventListener('click', counter.increaseCount)
}
К сожалению, слушатель событий не смог бы работать, так как this
значение было изменено. Вам понадобится то же самое, что и к классам — с функциями bind
или стрелками, чтобы заставить слушателя событий снова работать.
И это приводит меня ко второму пониманию.
Второе понимание this
Если вы используете синтаксис фабричной функции, вы не можете создавать методы с помощью стрелочных функций. Это связано с тем, что методы создаются в контексте простой функции.
function Counter (counterElement) {
// ...
const counter = {
// Do not do this.
// Doesn't work because `this` is `Window`
increaseCount: () => {
count = count + 1
this.updateCount()
}
}
// ...
}
Так что я настоятельно рекомендую полностью пропустить this
, если вы используете фабричные функции. Так намного проще.
Код
HTML
<div class="counter">
<p>Count: <span>0</span></p>
<button>Increase Count</button>
</div>
CSS
html {
font: 1em sans-serif;
}
body {
padding: 1em;
}
p {
font-size: 2em;
line-height: 1;
margin: 1rem 0;
}
button {
font: inherit;
padding: 0.5em 0.75em;
background-color: #eee;
border: 1px solid #222;
color: black;
}
Babel
function Counter (counterElement) {
const countElement = counterElement.querySelector('span')
const buttonElement = counterElement.querySelector('button')
let count = parseInt(countElement.textContent) || 0
const counter = {
updateCounter () {
countElement.textContent = count
},
increaseCount: (event) => {
count = count + 1
counter.updateCounter()
}
}
buttonElement.addEventListener('click', counter.increaseCount)
}
const counter = Counter(document.querySelector('.counter'))
Вердикт для слушателей события
Слушатели событий изменяют this
значение, поэтому мы должны быть очень осторожны в использовании значения this
. Если вы используете Классы, я рекомендую создавать обратные вызовы слушателей событий с стрелочными функциями, так что вам не придется использовать bind
.
Если вы используете фабричные функции, я рекомендую пропустить this
полностью, потому что это может сбить вас с толку. Вот и все!
Заключение
Мы говорили о четырех разновидностях Объектно-ориентированного программирования таких как:
Функции конструктора
Классы
OLOO
Фабричные функции
Во-первых, мы пришли к выводу, что Классы и фабричные функции легче использовать с точки зрения кода.
Во-вторых, мы сравнили, как использовать Подклассы с Классами и Фабричными функциями. Здесь мы видим, что создание Подклассов проще с Классами, но Состав проще с Фабричными функциями.
В-третьих, мы сравнили Инкапсуляцию с Классами и Фабричными функциями. Здесь мы видим, что инкапсуляция с фабричными функциями естественна, как и в JavaScript, в то время как инкапсуляция с классами требует добавления переменной
#
перед переменными.В-четвертых, мы сравнили использование
this
в Классах и Фабричных функциях. Я чувствую, что Фабричные функции здесь выигрывают, потому чтоthis
может быть неоднозначно. Написаниеthis.#privateVariable
также создает более длинный код по сравнению с использованием самойprivateVariable
.
Наконец, в этой статье мы построили простой Счетчик с Классами и Фабричными функциями. Вы научились добавлять слушателей событий в обе особенности Объектно-ориентированного программирования. Здесь обе особенности работают. Нужно просто быть осторожным, используете вы это или нет.
Вот и все!
Надеюсь, это прольет свет на объектно-ориентированное программирование на JavaScript для вас.
Если у вас есть вопросы по JavaScript или передовым разработкам в целом, не стесняйтесь обращаться ко мне. Посмотрим, чем я могу помочь!
Узнать подробнее о курсе "JavaScript Developer. Basic".
Посмотреть открытый урок на тему "Прототипное наследование в JavaScript".
Рекомендуем обратить внимание на смежные курсы:
JavaScript Developer. Professional
React.js Developer
Node.js Developer
ЗАБРАТЬ СКИДКУ