Объектно ориентированное программирование javascript: Объектно-ориентированный JavaScript простыми словами / Хабр

Содержание

Объектно-ориентированный JavaScript простыми словами / Хабр

Доброго времени суток, друзья!

В JavaScript существует 4 способа создать объект:

  • Функция-контруктор (constructor function)
  • Класс (class)
  • Связывание объектов (object linking to other object, OLOO)
  • Фабричная функция (factory function)

Какой метод следует использовать? Какой из них является лучшим?

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

Давайте начнем с того, что такое объектно-ориентированное программирование (ООП).

Что такое ООП?

По сути, ООП — это способ написания кода, позволяющий создавать объекты с помощью одного объекта. В этом также заключается суть шаблона проектирования «Конструктор».

Общий объект, обычно, называется планом, проектом или схемой (blueprint), а создаваемые с его помощью объекты — экземплярами (instances).

Каждый экземпляр имеет как свойства, наследуемые от родителя, так и собственные свойства. Например, если у нас имеется проект Human (человек), мы можем создавать на его основе экземпляры с разными именами.

Второй аспект ООП состоит в структурировании кода, когда у нас имеется несколько проектов разного уровня. Это называется наследованием (inheritance) или классификацией (созданием подклассов) (subclassing).

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

Перейдем с способам создания объектов.

Способы создания объекта


Функция-конструктор

Конструкторами являются функции, в которых используется ключевое слово «this».

    function Human(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }

this позволяет сохранять и получать доступ к уникальным значениям создаваемого экземпляра. Экземпляры создаются с помощью ключевого слова «new».

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier
const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew

Класс

Классы являются абстракцией («синтаксическим сахаром») над функциями-конструкторами. Они облегчают задачу создания экземпляров.

    class Human {
        constructor(firstName, lastName) {
            this.firstName = firstName
            this.lastName = lastName
        }
    }

Обратите внимание, что constructor содержит тот же код, что и функция-конструктор, приведенная выше. Мы должны это делать, чтобы инициализировать this. Мы может опустить constructor, если нам не требуется присваивать начальные значения.

На первый взгляд, классы кажутся сложнее, чем конструкторы — приходится писать больше кода. Придержите лошадей и не делайте поспешных выводов. Классы — это круто. Чуть позже вы поймете почему.

Экземпляры также создаются с помощью ключевого слова «new».

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Связывание объектов

Данный способ создания объектов был предложен Kyle Simpson. В данном подходе мы определяем проект как обычный объект. Затем с помощью метода (который, как правило, называется init, но это не обязательно, в отличие от constructor в классе) мы инициализируем экземпляр.

const Human = {
    init(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }
}

Для создания экземпляра используется Object. create. После создания экземпляра вызывается init.

const chris = Object.create(Human)
chris.init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Код можно немного улучшить, если вернуть this в init.

const Human = {
  init () {
    // ...
    return this
  }
}
const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Фабричная функция

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

Вот простой пример фабричной функции.

function Human(firstName, lastName) {
    return {
        firstName,
        lastName
    }
}

Для создания экземпляра нам не требуется ключевое слово «new». Мы просто вызываем функцию.

const chris = Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

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

Определение свойств и методов

Методы — это функции, объявленные в качестве свойств объекта.

    const someObject = {
        someMethod () { /* ... */ }
    }

В ООП существует два способа определения свойств и методов:

  • В экземпляре
  • В прототипе

Определение свойств и методов в конструкторе

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

function Human (firstName, lastName) {
  // Определяем свойства
  this.firstName = firstName
  this.lastname = lastName
  // Определяем методы
  this. sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}
const chris = new Human('Chris', 'Coyier')
console.log(chris)

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

Для добавления свойства в прототип используют prototype.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}
// Определяем метод в прототипе
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}

Создание нескольких методов может быть утомительным.

// Определение методов в прототипе
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }

Можно облегчить себе жизнь с помощью Object. assign.

Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})

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

Свойства экземпляра можно определить в constructor.

class Human {
  constructor (firstName, lastName) {
      this.firstName = firstName
      this.lastName = lastName
      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}

Свойства прототипа определяются после constructor в виде обычной функции.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

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

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }
  method1 () { /*. ..*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}

Определение свойств и методов при связывании объектов

Для определения свойств экземпляра мы добавляем свойство к this.

const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }
    return this
  }
}
const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)

Метод прототипа определяется как обычный объект.

const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Определение свойств и методов в фабричных функциях (ФФ)

Свойства и методы могут быть включены в состав возвращаемого объекта.

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console. log(`Hello, I'm ${firstName}`)
    }
  }
}

При использовании ФФ нельзя определять свойства прототипа. Если вам нужны такие свойства, можно вернуть экземпляр класса, конструктора или связывания объектов (но это не имеет смысла).

// Не делайте этого function createHuman (...args) { return new Human(...args) }

Где определять свойства и методы

Где следует определять свойства и методы? В экземпляре или в прототипе?

Многие считают, что для этого лучше использовать прототипы.

Однако на самом деле это не имеет особого значения.

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

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

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

Предварительный вывод

На основе изученного материала можно сделать несколько выводов. Это мое личное мнение.

  • Классы лучше конструкторов, поскольку в них легче определять несколько методов.
  • Связывание объектов кажется странным из-за необходимости использовать Object.create. Я постоянно забывал об этом при изучении данного подхода. Для меня это было достаточной причиной отказаться от его дальнейшего использования.
  • Классы и ФФ использовать проще всего. Проблема состоит в том, что в ФФ нельзя использовать прототипы. Но, как я отметил ранее, это не имеет особого значения.

Далее мы будем сравнивать между собой классы и ФФ как два лучших способа создания объектов в JavaScript.

Классы против ФФ — Наследование

Прежде чем переходить к сравнению классов и ФФ, необходимо познакомиться с тремя концепциями, лежащими в основе ООП:

  • наследование
  • инкапсуляция
  • this

Начнем с наследования.

Что такое наследование?

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

Это происходит двумя способами:

  • с помощью инициализации экземпляра
  • с помощью цепочки прототипов

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

Понимание создания подклассов

Создание подклассов — это когда дочерний проект расширяет родительский.

Рассмотрим это на примере классов.

Создание подклассов с помощью класса

Для расширения родительского класса используется ключевое слово «extends».

class Child extends Parent {
    // ...
}

Например, давайте создадим класс «Developer», расширяющий класс «Human».

// класс Human
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this. lastName = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Класс «Developer» будет расширять Human следующим образом:

class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }
    // ...
}

Ключевое слово «super» вызывает constructor класса «Human». Если вам это не нужно, super можно опустить.

class Developer extends Human {
  // ...
}

Допустим, Developer умеет писать код (кто бы мог подумать). Добавим ему соответствующий метод.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Вот пример экземпляра класса «Developer».

const chris = new Developer('Chris', 'Coyier')
console.log(chris)

Создание подклассов с помощью ФФ

Для создания подклассов с помощью ФФ необходимо выполнить 4 действия:

  • создать новую ФФ
  • создать экземпляр родительского проекта
  • создать копию этого экземпляра
  • добавить в эту копию свойства и методы

Данный процесс выглядит так.

function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    // Свойства и методы
  })
}

Создадим подкласс «Developer». Вот как выглядит ФФ «Human».

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

Создаем Developer.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    // Свойства и методы
  })
}

Добавляем ему метод «code».

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Создаем экземпляр Developer.

const chris = Developer('Chris', 'Coyier')
console.log(chris)

Перезапись родительского метода

Иногда возникает необходимость перезаписать родительский метод внутри подкласса. Это можно сделать следующим образом:

  • создать метод с тем же именем
  • вызвать родительский метод (опционально)
  • создать новый метод в подклассе

Данный процесс выглядит так.

class Developer extends Human {
  sayHello () {
    // Вызываем родительский метод
    super.sayHello()
    // Создаем новый метод
    console.log(`I'm a developer.`)
  }
}
const chris = new Developer('Chris', 'Coyier')
chris.sayHello()

Тот же процесс с использованием ФФ.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
      sayHello () {
        // Вызываем родительский метод
        human. sayHello()
        // Создаем новый метод
        console.log(`I'm a developer.`)
      }
  })
}
const chris = new Developer('Chris', 'Coyier')
chris.sayHello()

Наследование против композиции

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

Что же такое композиция?

Понимание композиции

По сути, композиция — это объединение нескольких вещей в одну. Наиболее распространенным и самым простым способом объединения объектов является использование Object.assign.

const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)

Композицию легче всего объяснить на примере. Допустим, у нас имеется два подкласса, Developer и Designer. Дизайнеры умеют разрабатывать дизайн, а разработчики — писать код. Оба наследуют от класса «Human».

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}
class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}
class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Теперь предположим, что мы хотим создать третий подкласс. Этот подкласс должен быть смесью дизайнера и разработчика — он должен уметь как разрабатывать дизайн, так и писать код. Назовем его DesignerDeveloper (или, если угодно, DeveloperDesigner).

Как нам его создать?

Мы не может одновременно расширить классы «Designer» и «Developer». Это невозможно, поскольку мы не можем решить, какие свойства должны быть первыми. Это называется проблемой ромба (ромбовидным наследованием).

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

// Не работает
class DesignerDeveloper extends Developer, Designer {
  // ...
}

Здесь нам пригодится композиция.

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

Реализация этого подхода приводит к следующему.

const skills = {
    code (thing) { /* ... */ },
    design (thing) { /* ... */ },
    sayHello () { /* ... */ }
}

Нам больше не нужен класс «Human», ведь мы можем создать три разных класса с помощью указанного объекта.

Вот код для DesignerDeveloper.

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    Object.assign(this, {
      code: skills. code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}
const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)

Мы можем сделать тоже самое для Designer и Developer.

class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}
class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    })
  }
}

Вы заметили, что мы создаем методы в экземпляре? Это лишь один из возможных вариантов. Мы также можем поместить методы в прототип, но я нахожу это лишним (при таком подходе кажется, что мы вернулись к конструкторам).

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this. firstName = firstName
    this.lastName = lastName
  }
}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})

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

Композиция с помощью ФФ

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

function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}

Наследование и композиция

Никто не говорил, что мы не можем использовать наследование и композицию одновременно.

Возвращаясь к примеру с Designer, Developer и DesignerDeveloper, нельзя не отметить, что они также являются людьми. Поэтому они могут расширять класс «Human».

Вот пример наследование и композиции с использованием синтаксиса классов.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}
class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})

А вот тоже самое с использованием ФФ.

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}
function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  })
}

Подклассы в реальном мире

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

Например: событие «click» является MouseEvent (событием мыши). MouseEvent — это подкласс UIEvent (событие пользовательского интерфейса), который, в свою очередь, является подклассом Event (событие).

Другой пример: HTML Elements (элементы) являются подклассами Nodes (узлов). Поэтому они могут использовать все свойства и методы узлов.

Предварительный вывод относительно наследования

Наследование и композиция могут использоваться как в классах, так и в ФФ. В ФФ композиция выглядит «чище», но это незначительное преимущество перед классами.

Продолжим сравнение.

Классы против ФФ — Инкапсуляция

По сути, инкапсуляция — это скрытие одной вещи внутри другой, из-за чего внутренняя сущность становится недоступной снаружи.

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

Простая инкапсуляция

Простейшей формой инкапсуляции является блок кода.

{
  // Переменные, объявленные здесь, будут иметь блочную область видимости
}

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

const food = 'Hamburger'
{
  console.log(food)
}

Но не наоборот.

{
  const food = 'Hamburger'
}
console.log(food)

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

Инкапсуляция с помощью функции

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

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())

Замыкание

Замыкание — это продвинутая форма инкапсуляции. Это просто функция внутри другой функции.

// Пример замыкания
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)
  }
}
// Вызываем outsideFunction, которая возвращает insideFunction
// Сохраняем insideFunction в переменной "fn"
const fn = outsideFunction()

Инкапсуляция и ООП

При создании объектов мы хотим, чтобы одни свойства были открытыми (публичными), а другие закрытыми (частными или приватными).

Рассмотрим пример. Скажем, у нас имеется проект «Car». При создании нового экземпляра мы добавляем ему свойство «fuel» (топливо) со значением 50.

class Car {
  constructor () {
    this.fuel = 50
  }
}

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

const car = new Car()
console.log(car.fuel) // 50

Пользователи также могут самостоятельно устанавливать количество топлива.

const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000

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

Существует два способа это сделать:

  • использование частных свойств по соглашению
  • использование настоящих частных полей

Частные свойства по соглашению

В JavaScript частные переменные и свойства, обычно, обозначаются с помощью нижнего подчеркивания.

class Car {
  constructor () {
    // Отмечаем свойство "fuel" как частное, которое не должно использоваться за пределами класса
    this._fuel = 50
  }
}

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

class Car {
  constructor () {
    this._fuel = 50
  }
  getFuel () {
    return this._fuel
  }
  setFuel (value) {
    this._fuel = value
    // Определяем вместимость бака
    if (value > 100) this._fuel = 100
  }
}

Для определения и установки количества топлива пользователи должны использовать методы «getFuel» и «setFuel», соответственно.

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
  }
}

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

Частные переменные должны определяться вне конструктора.

class Car {
  // Определяем частную переменную
  #fuel
  constructor () {
    // Используем ее
    this.#fuel = 50
  }
}

В данной случае мы можем инициализировать переменную при определении.

class Car {
  #fuel = 50
}

Теперь переменная «#fuel» доступна только внутри класса. Попытка получить к ней доступ за пределами класса приведет к возникновению ошибки.

const car = new Car()
console.log(car.#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

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

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

Для управления частной переменной «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.

Названными контекстами являются:

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

Но вернемся к статье. Давайте рассмотрим особенности использования 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 указывает на window (в контексте модуля this вообще имеет значение «undefined»).

// Для создания экземпляра не используется ключевое слово "new"
function Human (firstName, lastName) {
  this. firstName = firstName
  this.lastName = lastName
  console.log(this)
}
const chris = Human('Chris', 'Coyier')

Таким образом, в ФФ не следует использовать 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 явно указывает на экземпляр.

На самом деле нам даже не нужно писать human.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()

Рассмотрим более сложный пример.

Сложный пример

Условия таковы: у нас имеется проект «Human» со свойствами «firstName» и «lastName» и методом «sayHello».

Также у нас имеется проект «Developer», наследующий от Human. Разработчики умеют писать код, поэтому у них должен быть метод «code». Кроме того, они должны заявлять о своей принадлежности к касте разработчиков, поэтому нам необходимо перезаписать метод «sayHello».

Реализуем указанную логику с помощью классов и ФФ.

Классы

Создаем проект «Human».

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Создаем проект «Developer» с методом «code».

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Перезаписываем метод «sayHello».

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}

ФФ (с использованием this)

Создаем проект «Human».

function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console. log(`Hello, I'm ${this.firstName}`)
    }
  }
}

Создаем проект «Developer» с методом «code».

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Перезаписываем метод «sayHello».

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)

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

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 не имеет значения, если мы знаем, как с этим справиться. Рассмотрим простой пример.

Создание счетчика

Для создания счетчика воспользуемся полученными знаниями, включая частные переменные.

Наш счетчик будет содержать две вещи:

  • сам счетчик
  • кнопку для увеличения его значения

Вот как может выглядеть разметка:

<div>
  <p>Count: <span>0</span></p>
  <button>Increase Count</button>
</div>

Создание счетчика с помощью класса

Для облегчения задачи попросим пользователя найти и передать разметку счетчика классу «Counter»:

class Counter {
  constructor (counter) {
    // ...
  }
}
// Использование
const counter = new Counter(document.querySelector('.counter'))

В классе необходимо получить 2 элемента:

  • <span>, содержащий значение счетчика — нам нужно обновлять это значение при увеличении счетчика
  • <button> — нам нужно добавить обработчик событий, вызываемых данным элементом
class Counter {
  constructor (counter) {
    this. countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}

Далее мы инициализируем переменную «count» текстовым содержимым countElement. Указанная переменная должна быть частной.

class Counter {
  #count
  constructor (counter) {
    // ...
    this.#count = parseInt(countElement.textContent)
  }
}

При нажатии кнопки значение счетчика должно увеличиваться на 1. Реализуем это с помощью метода «increaseCount».

class Counter {
  #count
  constructor (counter) { /* ... */ }
  increaseCount () {
    this.#count = this.#count + 1
  }
}

Теперь нам необходимо обновить DOM. Реализуем это с помощью метода «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)
  }
  // Методы
}

Исключение выбрасывается, потому что this указывает на buttonElement (контекст обработчика событий). В этом можно убедиться, если вывести значение this в консоль.

Значение this необходимо изменить таким образом, чтобы оно указывало на экземпляр. Это можно сделать двумя способами:

  • с помощью bind
  • с помощью стрелочной фукнции

Большинство использует первый способ (однако второй проще).

Добавление обработчика событий с помощью bind

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

class Counter {
  // ...
  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
  }
  // ...
}

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

Стрелочные функции

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

class Counter {
  // ...
  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', () => {
      this.increaseCount()
    })
  }
  // Методы
}

Есть еще более простой способ. Мы можем создать increaseCount в виде стрелочной функции. В этом случае this будет указывать на экземпляр.

class Counter {
  // ...
  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }
  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }
  // ...
}

Код

Вот полный код примера:

Создание счетчика с помощью ФФ

Начало аналогичное — мы просим пользователя найти и передать разметку счетчика:

function Counter (counter) {
  // ...
}
const counter = Counter(document.querySelector('.counter'))

Получаем необходимые элементы, которые по умолчанию будут частными:

function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')
}

Инициализируем переменную «count»:

function Counter (counter) {
  const countElement = counter. querySelector('span')
  const buttonElement = counter.querySelector('button')
  let count = parseInt(countElement.textContext)
}

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

function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
    }
  }
}

DOM будет обновляться с помощью метода «updateCount», который вызывается внутри increaseCount:

function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
      counter.updateCount()
    },
    updateCount () {
      increaseCount()
    }
  }
}

Обратите внимание, что вместо this.updateCount мы используем counter.updateCount.

Добавление обрабочика событий

Мы можем добавить обработчик событий к buttonElement, используя counter. increaseCount в качестве колбэка.

Это будет работать, поскольку мы не используем this, поэтому для нас не имеет значения то обстоятельство, что обработчик меняет контекст this.

function Counter (counterElement) {
  // Переменные
  // Методы
  const counter = { /* ... */ }
  // Обработчики событий
  buttonElement.addEventListener('click', counter.increaseCount)
}

Первая особенность this

Вы можете использовать this в ФФ, но только в контексте метода.

В следующем примере при вызове counter.increaseCount будет вызван counter.updateCount, поскольку this указывает на counter:

function Counter (counterElement) {
  // Переменные
  // Методы
  const counter = {
    increaseCount() {
      count = count + 1
      this.updateCount()
    }
  }
  // Обработчики событий
  buttonElement.addEventListener('click', counter.increaseCount)
}

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

Вторая особенность this

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

function Counter (counterElement) {
  // ...
  const counter = {
    // Не делайте так
    // Не работает, поскольку this указывает на window
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}

Поэтому при использовании ФФ я настоятельно рекомендую избегать использования this.

Код
Вердикт относительно обработчиков событий

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

При использовании ФФ рекомендую вообще обходиться без this.

Заключение

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

  • Функции-конструкторы
  • Классы
  • Связывание объектов
  • Фабричные функции

Во-первых, мы пришли к выводу, что наиболее оптимальными способами создания объектов являются классы и ФФ.

Во-вторых, мы увидели, что подклассы легче создавать с помощью классов. Однако, в случае композиции лучше использовать ФФ.

В-третьих, мы резюмировали, что, когда речь идет об инкапсуляции, ФФ имеют преимущество перед классами, поскольку последние требуют использования специального префикса «#», а ФФ делают переменные частными автоматически.

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

На этом у меня все. Надеюсь, статья вам понравилась. Благодарю за внимание.

Объектно-ориентированное программирование в JavaScript. Прототипы OTUS

Объектно-ориентированное программирование – способ создания контента, базирующийся на разнообразных элементах. Их можно визуализировать, что значительно упрощает программирование. Логика и «голый код» здесь не выступают в качестве основополагающих.

JavaScript – это скриптовый язык программирования. Используется для разнообразных целей – от написания аналитических утилит до игр. Основное его применение – это веб-утилиты и браузерный контент. Но элементы ООП в нем все равно присутствуют.

В данной статье будет рассказано о том, что такое прототипы, как ими пользоваться. А еще – раскрыты ключевые особенности JS как способа ООП. Полученные знания пригодятся всем новичкам-разработчикам.

ООП – что такое

Объектно-ориентированное программирование – способ коддинга, который позволяет создавать разнообразные объекты посредством другого объекта. В процессе проектирования задействован так называемый конструктор. Его принц работы основан на объектах, их создании и взаимодействии.

Общий объект – это план. Может также носить название проекта или схемы. Создаваемые посредством оного элементы – экземпляры.

Аспекты

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

  1. Каждый экземпляр класса обладает свойствами, которые наследуются от родителей. Также есть собственные параметры.
  2. Структурированный код предусматривает возможность размещения нескольких уровней в проекте. Процедура носит название наследования или классификации. Во втором случае целесообразно говорить о создании подклассов.
  3. Инкапсуляция помогает скрывать детали реализации кодификации от сторонних глаз. Это приводит к тому, что функции и переменные, а также иные важные объекты приложения становятся не доступными извне. Таковая концепция шаблонов проектирования «Фасад» и «Модуль».

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

Чуть-чуть терминологии – ключевые понятия

Для того, чтобы решать разнообразные задачи программирования, важно разобраться с терминологией. Пока она не изучена, осознание коддинга не придет. Даже опытные разработчики должны помнить о «базе»:

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

Этого новичкам будет более чем достаточно. Теперь можно рассмотреть объекты в JavaScript более подробно, особое внимание уделив таким элементам, как прототипы.

Информация об объектах – что и как

Java Script – это скриптовый язык программирования, который предусматривает весьма мощный функционал. Если реализовывать его грамотно и правильно, можно создавать не только небольшие веб-проекты, но и решать сложные масштабные задачи.

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

  • функция – конструктор;
  • класс;
  • связывание уже имеющихся объектов;
  • фабричная функция.

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

Функция – конструктор

Первый вариант – это создание элементов через функцию-конструктор. Конструктор – это функция, в которой задействовано ключевое слово под названием this.

This дает возможность получения доступа и сохранения уникальных значений создаваемых экземпляров. Экземпляр можно «добавить» посредством ключевика new.

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

Классы в помощь

Следующий вариант развития событий – это использование классов. Они в JS выступают в качестве абстракций (неким «синтаксическим сахаром) над функциями-конструкторами. Посредством соответствующих элементов удается быстрее и проще создать экземпляры:

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

  1. Constructor имеет такой же код, как и функция-конструктор, рассмотренная ранее. Такой прием необходим для инициализации this.
  2. Опустить constructor можно, если не нужно присваивать начальные значения.
  3. Добавление экземпляров происходит тоже при помощи ключевого слова new.

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

Связка

Третий подход к созданию объектов в Java Script – это использование связывания ранее имеющихся в кодификации оставляющих. Метод был предложен Кейли Симпсон. Здесь проект определяется в виде обычного объекта. Далее за счет метода (он чаще всего носит название init) происходит инициализация экземпляра.

Для того, чтобы создать экземпляр, необходимо применить Object.create. После реализации задачи происходит вызов init.

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

Но это еще не все. Посмотрим на еще один, последний подход к созданию объектов в JS.

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

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

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

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

О методах и свойствах

Метод – функция, которая объявлена в качестве свойства того или иного объекта в кодификации JS.

Определений свойств и методов в объектно-ориентированном программировании несколько. А именно:

  • через экземпляры;
  • путем работы с прототипом.

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

Прототип – это что

Прототип – элемент JS, который позволяет другим составляющих кода наследовать свойства и методы. Изначально каждый объект обладает собственным прототипом. Если у искомого элемента не хватает параметров и свойств, они будут искаться в prototype. Когда у ближайшего прототипа соответствующие характеристики отсутствуют, поиск переходит далее по иерархии – ниже. Описанный принцип – наследование прототипов в JS.

Управление соответствующими составляющими не слишком трудное. В самом верху цепочки прототипов расположен последний (стандартный объект ДжаваСкрипт). Выше него совершенно ничего нет.

Прототип объекта

Стоит обратить внимание на очень важные моменты программирования объектно-ориентированного характера. А именно – на prototype. Он может относиться к функции или объекту.

Во втором случае рекомендуется рассмотреть наглядный пример:

Здесь происходит следующее:

  1. Для того, чтобы увидеть __photo__ в консоли разработчика, создается составляющая под названием «машина».
  2. В консоли выводится тип данных – object. Внутри него расположена ссылка __photo__.
  3. Последняя приведет к прототипу объекта car.
  4. Внутри ссылки отображается вся цепочка prototypes. Она рано или поздно получит значение null (пусто, ничего).

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

Прототипы функций

А вот еще один весьма важный момент – прототипы функций. У каждой подобной составляющей есть свойство под названием prototype.

Здесь происходит следующее:

  1. Создается новый элемент user вторым рассматриваемым способом.
  2. Внутри соответствующей составляющей прописываются два свойства и одна функция.
  3. Последняя отвечает за вывод в консоль строчки Privet.
  4. Теперь в консоль нужно вывести объект user с ранее знакомой ссылкой __photo__.
  5. Если открыть соответствующую ссылку, среди предложенных методов отобразится toString. Это значит, что метод перешел к «юзеру» посредством наследования.

У всех новых элементов всегда есть прототип. Вот пример обращения к свойству prototype глобального класса Object. Также предложенный ниже код поможет создать новое поле – функцию sayHello.

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

Стоит обратить внимание на то, что у user изначально не было функции sayHello. Она возникла у родителя оного. Отсюда следует, что user получил ее в качестве «наследства» от родителя – прототипа Object.

Определение свойств и методов в конструкторе

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

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

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

Добавление свойства в прототипы предусматривает использование prototype.

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

Облегчить управление методами (добавление оных) удается через Object.assign.

Выше представлен код, который делает манипулирование созданием методов и свойств более быстрым и простым.

Свойства и методы в классе

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

Здесь необходимо запомнить следующую информацию:

  • Свойства экземпляра определяются в constructor.
  • Свойства прототипа будут определяться после конструктора в качестве «обычной» функции.
  • Создавать несколько методов в классе проще, чем через конструктор. Для этого не нужно использоваться Object.assign. Достаточно просто произвести добавление иных функций.

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

Свойства и методы при связывании элементов

Пока утилита находится в стадии разработки, важно определиться с тем, как прописывать свойства и методы. Есть вариант «при связывании элементов кодификации».

Для того, чтобы определить свойства экземпляра, требуется добавить его к this.

Метод прототипа будет определяться в качестве обычного объекта.

Кодификация выше демонстрирует принцип реализации оного.

Определение в фабричных функциях

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

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

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

Как быстро выучить материал

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

Такой вариант имеет немало преимуществ. Среди них выделяют:

  • удобство – можно совмещать с семьей, работой или «основной» учебой;
  • доступность;
  • дистанционная организация образовательного процесса;
  • сопровождение опытными разработчиками-кураторами;
  • море практики;
  • хорошо составленная программа;
  • наличие предложений для пользователей с разным уровнем знаний и навыков;
  • возможность сконцентрироваться на одном или нескольких направлениях/языках.

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

учебник для начинающих по веб-разработке

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

Если вы программируете на JavaScript, знакомство с принципами ООП может облегчить вашу жизнь по нескольким причинам:

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

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

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

Что такое ООП (объектно-ориентированное программирование)?

Если вы знакомы с другими языками, такими как C# и Java, то вы, вероятно, слышали термин объектно-ориентированное программирование (ООП).

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

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

ООП в JavaScript (ES5)

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

Вы, наверное, видели, что в других языках, таких как C++, Java и C#, для определения класса используется ключевое слово class. Класс имеет свойства и методы для каждого экземпляра этого класса. В этом случае класс действует как план объекта.

JavaScript отличается от других языков тем, что вы можете реализовать ООП без использования классов (подробнее об этом позже). До появления версии ES2015 JavaScript по-прежнему полагался на программирование на основе прототипов. В этом стиле программирования объект инкапсулирует свойства, т. е. его методы и данные, а не класс. Вы можете добавить новые свойства к этому объекту в любое время. Итак, теперь объект может быть отдельным, а не экземпляром класса, то есть, если вам нужен объект, вы легко можете просто создать его без необходимости сначала создавать класс.

И ООП на основе прототипов, и ООП на основе классов имеют свои преимущества и недостатки.

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

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

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

Объекты в JavaScript

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

Что такое объект?

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

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

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

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

  • Значения данных
  • Функции

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

Точно так же потребуется функция для вычисления площади. Это также будет инкапсулировано в объект как часть его свойств.

Как создать литерал объекта

Литерал объекта может быть создан:

  • Использование фигурных скобок {…} в объявлении.
  • Использование нового ключевого слова.
  • На основе существующего объекта с помощью метода create().

Все эти подходы делают одно и то же.

Вот как выглядит синтаксис:

Доступ к свойствам объекта

Существуют различные способы доступа к свойствам объекта. Описано несколько популярных способов, но вы также можете перебирать свойства объекта, используя цикл for..in, и вы также можете получить доступ к свойствам вложенного цикла (для реализации этого все, что требуется, это использовать оператор точка, но вам понадобится добавить еще одну точку).

Оператор точки (также полезен для установки и удаления свойств)

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

Здесь мы можем увидеть синтаксис оператора точки:

Вот пример того, как получить доступ к свойствам с помощью оператора точки:

Использование квадратных скобок (также полезно для установки и удаления свойств)

Другой способ доступа к значениям — использование квадратных скобок [ ]. Имя свойства, к которому осуществляется доступ, записывается в квадратных скобках в виде строки.

Здесь мы можем увидеть синтаксис метода квадратных скобок:

Вот пример доступа к свойствам с помощью квадратных скобок:

Полезные ключевые слова: Get, Set, This

Получить

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

Установить

Синтаксис set связывает свойство объекта с функцией, которая будет вызываться при попытке установить это свойство.

Это

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

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

Функции конструктора

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

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

Вот синтаксис реализации функции конструктора:

Как видно сверху:

  • Ключевое слово function используется для определения функции.
  • Имя функции-конструктора должно быть написано с заглавной буквы, как и FunctionName в приведенном выше фрагменте.
  • Тело этой функции в основном представляет собой часть конструктора функции, поскольку оно инициализирует свойства, устанавливая их равными соответствующим параметрам, передаваемым в функцию.

Вот пример функции-конструктора:

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

Объекты-прототипы

Объекты-прототипы — это более простой способ добавления новых методов/свойств в функцию-конструктор.

Свойства прототипа в объектах

Помимо свойств, которые вы создаете, есть дополнительное скрытое свойство, известное как свойство [[Prototype]], которое присутствует внутри каждого объекта, созданного из функции-конструктора. Свойство прототипа либо указывает на другой объект, либо имеет значение null.

Вот пример использования свойства Prototype:

Здесь мы видим, что когда для свойства прототипа Rectangle установлено значение Shape, он может получить доступ ко всем свойствам в Shape. Если свойство не найдено в объекте, например свойство name не найдено в прямоугольнике, JavaScript автоматически возьмет его из прототипа этого объекта, формы. Это известно как прототипное наследование, где строки 20 и 21 известны как унаследованные свойства; это основано на концепции цепочки прототипов.

Объектно-ориентированный JavaScript в ES6

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

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

Объявление класса в JavaScript ES6

Синтаксис следующий:

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

Создание экземпляра объекта из класса

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

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

Определение методов в классе

Всякий раз, когда метод объявляется внутри класса, он определяется в прототипе этого класса. Это означает, что всякий раз, когда экземпляр объекта обращается к нему, он берется из прототипа соответствующего класса.

Вот пример:

Вот что происходит в приведенном выше коде:

  • Функция getAge определяется вне функции конструктора в строке 15.
  • Все такие методы хранятся в объекте-прототипе сотрудника.
  • Итак, новый объект, такой как employeeObj, имеет доступ ко всем методам, определенным в классе.
  • При вызове employeeObj метод getAge берется из employee.prototype.

Следующие шаги

Хотя JavaScript нельзя считать ООП-языком, использование версии ES6 (из-за использования классов) даст вам представление о том, что значит писать код на более традиционном ООП-языке, таком как C/C++. Основное различие между ES5 и ES6 заключается в добавлении и очистке синтаксиса.

Этот пост только поверхностно коснулся объектно-ориентированного JavaScript. Можно охватить гораздо больше: статические методы, защитные свойства и инкапсуляция данных, и это лишь некоторые из них. Если вам интересно углубиться в детали, вы можете изучить все основы с ООП в JavaScript.

Дальнейшие чтения

Статья: Дорожная карта становления Front-End разработчиком

Статья: Руководство по веб-разработке для начинающих

Статья: Часто задаваемые вопросы веб-разработчика

Курс: Изучаем веб-разработку с нуля

Курс: Веб-разработка: основы

Объектно-ориентированное программирование на JS — Айтилогия

Объектно-ориентированное программирование на JS

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

Веб-разработка

7 авг. 2020

Что такое объектно-ориентированное программирование?

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

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

Правильно спроектированные объекты обеспечивают простой интерфейс и скрывают ненужную сложность снаружи. Например, DVD-плеер имеет сложную логическую плату внутри и всего 3 или 4 кнопки снаружи. Когда вы нажимаете кнопку воспроизведения, то даже не задумываетесь, каким образом все микрочипы общаются друг с другом.

Зачем знать ООП?

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

ООП на JavaScript

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

Что такое объект?

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

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

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

Классы

Классы в ООП используются как шаблоны для создания объектов. Объект — это «экземпляр класса», а «создание экземпляра» — это создание объекта на основе класса. Код определен в классе, но не может выполняться, если он не находится в живом объекте.

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

Абстракция

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

Отсутствие абстракции в коде может привести к проблемам с его отладкой.

Наследование


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

Инкапсуляция

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

Объекты должны иметь контроль над своими данными. Другими словами, они «инкапсулируют» свои данные и препятствуют прямому доступу к данным другим объектам. Единственный способ получить доступ к данным — косвенный, через функции, записанные в сами объекты.


Объектно-ориентированное программирование – это­ сложная тема, особенно для новичков. Навык написания кода с использованием этой парадигмы поможет стать высококлассным разработчиком, экономить время, писать чистый и понятный код и эффективно использовать код повторно.

Читайте другие статьи

Bootstrap 5: преимущества и обновления

А вы слышали, что самый популярный среди веб-разработчиков фреймворк обновлен до новой версии и имеет еще больше полезных функций? В статье разбираемся, что такое Bootstrap и какие новые фишки внесли разработчики в 5 версию.

Веб-разработка

2 апр. 2021

Рефакторинг кода

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

Веб-разработка

10 нояб. 2020

10 ресурсов для изучения Vue.js в 2020 году

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

Веб-разработка

29 сент. 2020

Варианты объектно-ориентированного программирования (на JavaScript)

Spread the love

Перевод: Zell Liew — The Flavors of Object-Oriented Programming (in JavaScript)

В своем исследовании я рассмотрел четыре подхода к объектно-ориентированному программированию в JavaScript:

  1. Использование функций конструктора
  2. Использование классов
  3. Объекты, связанные с другими объектами (OLOO)
  4. Использование фабричных функций

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

Чтобы принять это решение, мы не просто рассмотрим разные подходы, но и сравним их концептуальные аспекты:

  1. Классы против Фабричных функций — Наследование
  2. Классы против Фабричных функций — Инкапсуляция
  3. Классы против Фабричных функций — this
  4. Классы против Фабричных функций — Event listeners

Начнем с основ ООП в JavaScript.

Что такое объектно-ориентированное программирование?

Объектно-ориентированное программирование — это способ написания кода, который позволяет создавать разные объекты из объекта. Общий объект обычно называется blueprint (базовая схема), а созданные объекты — экземплярами.

У каждого экземпляра есть свойства, которые не используются другими экземплярами. Например, если у вас есть blueprint человека, вы можете создавать экземпляры людей с разными именами.

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

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

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

Начнем с основ — введение в четыре разновидности объектно-ориентированного программирования.

Четыре разновидности объектно-ориентированного программирования

Есть четыре способа использовать объектно-ориентированное программирование на JavaScript:

  1. Использование функции конструктора
  2. Использование классов
  3. Использование объектов, связанные с другими объектами
  4. Использование фабричных функций
Использование функций конструктора

Конструкторы — это функции, содержащие ключевое слово this.

function Human (firstName, lastName) {
  this. firstName = firstName
  this.lastName = lastName
}

this позволяет хранить уникальные значения, созданные для каждого экземпляра. Вы можете создать экземпляр с ключевым словом new.

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier
const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew
Синтаксис Class

Ключевое слово class считаются «синтаксическим сахаром» функций-конструкторов.

Существуют серьезные разногласия по поводу того, являются ли использование классов плохой практикой (например, это и это). Мы не собираемся здесь углубляться в эти аргументы. Вместо этого мы просто посмотрим, как писать код с помощью классов, и решим, лучше ли классы, чем конструкторы, на основе кода, который мы пишем.

Классы могут быть написаны со следующим синтаксисом:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Обратите внимание, что функция constructor содержит тот же код, что и синтаксис конструктора выше? Нам используем это, поскольку мы хотим инициализировать значения для this. (Мы можем пропустить constructor , если нам не нужно инициализировать значения. Подробнее об этом позже в разделе «Наследование»).

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

Как и раньше, вы можете создать экземпляр с ключевым словом new.

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier
Объекты, связанные с другими объектами (OLOO —
Objects Linking to Other Objects)

OLOO был придуман и популяризирован Kyle Simpson. В OLOO вы определяете blueprint как обычный объект. Затем вы используете метод (часто называемый init, но он не требуется, как конструктор для класса) для инициализации экземпляра.

const Human = {
  init (firstName, lastName ) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Далее используете Object. create для создания экземпляра. После создания экземпляра необходимо запустить функцию инициализации.

const chris = Object.create(Human)
chris.init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Вы можете связать init после Object.create, если вы вернули его внутри init.

const Human = {
  init () {
    // ...
    return this 
  }
}
const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier
Фабричные функции

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

Вот самый простой способ создания фабричной функций:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName
  }
}

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

const chris = Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

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


Объявление свойств и методов

Методы — это функции, объявленные как свойство объекта.

const someObject = {
  someMethod () { /* ... */ }
}

В объектно-ориентированном программировании есть два способа объявления свойств и методов:

  1. Непосредственно на экземпляре
  2. В прототипе

Давай научимся делать и то, и другое.

Объявление свойств и методов с помощью конструкторов

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

function Human (firstName, lastName) {
  // Declares properties
  this. firstName = firstName
  this.lastname = lastName
  // Declares methods
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}
const chris = new Human('Chris', 'Coyier')
console.log(chris)

Методы обычно объявляются в Prototype, потому что Prototype позволяет экземплярам использовать один и тот же метод. Это типа как «отпечаток кода».

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

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}
// Declaring method on a prototype
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}

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

// Declaring methods on a prototype
Human. prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }

Вы можете упростить задачу, используя функции слияния, например с помощью Object.assign.

Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})

Object.assign не поддерживает объединение функций Getter и Setter. Вам нужен другой инструмент. Вот почему. А вот инструмент, который я создал для объединения объектов с Getter и Setter.

Объявление свойств и методов с помощью классов

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

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName
      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}

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

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Объявлять несколько методов в классах так же проще, чем в конструкторах. Вам не нужен синтаксис Object. assign. Вы просто пишете больше функций.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }
  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}
Объявление свойств и методов с помощью OLOO

Вы можете использовать тот же процесс для объявления свойств и методов в экземпляре. Для этого назначьте их как свойство this.

const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }
    return this
  }
}
const chris = Object. create(Human).init('Chris', 'Coyier')
console.log(chris)

Чтобы объявить методы, просто опишите метод как обычный объект.

const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}
Объявление свойств и методов с помощью фабричных функций

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

function Human (firstName, lastName) {
  return {
    firstName,
    lastName, 
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

Нельзя объявлять методы в прототипе при использовании фабричной функций. Если вам действительно нужны методы в прототипе, вам нужно вернуть экземпляр Constructor, Class или OLOO. (Но не делайте этого, потому что в этом нет никакого смысла.)

// Do not do this
function createHuman (...args) {
  return new Human(...args)
}

Где объявлять свойства и методы

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

Многие люди гордятся тем, что JavaScript — это «язык прототипов» (что означает, что он использует прототипы). Из этого утверждения вы можете сделать предположение, что использование «Прототипов» лучше.

Настоящий ответ таков: это не имеет значения.

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

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

Я написал отдельную статью о понимании прототипов JavaScript, если вам интересно узнать больше.

Предварительный вердикт

Резюмируем, то что я написал выше. Но учтите это только мое собственное мнение!

  1. Классы лучше, чем конструкторы, потому что в классах проще написать несколько методов.
  2. Использование OLOO довольно неудобно из-за Object.create. Некоторое время я пробовал использовать OLOO, но я всегда забываю написать Object.create.
  3. Проще всего использовать классы и фабричные функции. Проблема в том, что фабричные функции не поддерживают прототипы. Но, как я уже сказал, это не всегда имеет значение.

Остался один вопрос. Что нам выбрать классы или фабричные функции? Давай сравним их!


Классы против фабричных функций — Наследование

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

  1. Наследование
  2. Инкапсуляция
  3. this

Начнем с наследования.

Что такое наследование?

Наследование — это многозначное слово. На мой взгляд, многие в индустрии неправильно используют наследование. Слово «наследование» используется, когда вы получаете что-то откуда-то. Например:

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

Довольно просто.

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

Это означает, что все экземпляры фактически наследуют свои blueprint. Они наследуют свойства и методы двумя способами:

  1. путем создания свойства или метода непосредственно при создании экземпляра
  2. через цепочку прототипов

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

Понимание
Subclassing

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

Поговорим об этом с синтаксисом класса, потому что его легче понять.

Создание Subclassing  с помощью класса

Для создания Subclass, нужно использовать ключевое слово extends.

class Child extends Parent {
  // ... Stuff goes here
}

Например, предположим, что мы хотим создать класс Developer из класса Human.

// Human Class
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Класс Developer унаследуется от Human следующим образом:

class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }
    // Add other methods
}

Примечание: super вызывает класс Human (также называемый «родительским») классом. Точнее он запускает constructor из Human. Если вам не нужен дополнительный код запуска, вы можете полностью пропустить constructor .

class Developer extends Human {
  // Add other methods
}

Допустим, Developer может писать код. Тогда мы можем добавить метод code прямо в Developer.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Вот пример экземпляра Developer:

const chris = new Developer('Chris', 'Coyier')
console. log(chris)
Subclassing с помощью фабричных функций

Есть четыре шага для создания Subclass с фабричными функциями:

  1. Создайте новую фабричную функцию
  2. Создайте экземпляр родительского класса
  3. Создать новую копию этого экземпляра
  4. Добавьте свойства и методы к этой новой копии

Процесс выглядит так:

function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    // Properties and methods go here
  })
}

Мы можем использовать тот же пример — создание подкласса Developer — чтобы проиллюстрировать этот процесс. Вот фабричная функция Human:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console. log(`Hello, I'm ${firstName}`)
    }
  }
}

Далее создаем Developer следующим образом:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    // Properties and methods go here
  })
}

Затем добавляем метод code:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Вот пример создания экземпляра Developer:

const chris = Developer('Chris', 'Coyier')
console. log(chris)

Примечание. Вы не можете использовать Object.assign, если используете геттеры и сеттеры. Вам понадобится другой инструмент, например микс. Я объясняю это в этой статье.

Переопределение родительского метода

Иногда вам нужно переопределить родительский метод внутри подкласса. Это можно сделать так:

  1. Создать одноименный метод
  2. Вызвать родительский метод (необязательно)
  3. Изменить все, что нужно, в методе подкласса

С классами процесс выглядит так:

class Developer extends Human {
  sayHello () {
    // Calls the parent method
    super.sayHello() 
    // Additional stuff to run
    console.log(`I'm a developer.`)
  }
}
const chris = new Developer('Chris', 'Coyier')
chris.sayHello()

С фабричными функциями это выглядит так:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object. assign({}, human, {
      sayHello () {
        // Calls the parent method
        human.sayHello() 
        // Additional stuff to run
        console.log(`I'm a developer.`)
      }
  })
}
const chris = new Developer('Chris', 'Coyier')
chris.sayHello()
Наследование против Композиции

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

«Предпочитайте композицию объектов наследованию классов», «Design Patterns: Elements of Reusable Object Oriented Software»

«В информатике составной тип данных — это любой тип данных, который может быть сконструирован в программе с использованием примитивных типов данных языка программирования и других составных типов. […] Построение составного типа известно как композиция ». ~ Википедия

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

Понимание композиции

Композиция — это соединение двух вещей вместе. Речь идет о слиянии. Самый распространенный (и самый простой) способ объединения объектов — Object.assign.

const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)

Использование композиции можно лучше объяснить на примере. Допустим, у нас уже есть два подкласса: Designer и Developer. Designers могут проектировать, а Developer могут писать код. И Designers, и Developer наследуют от класса Human.

Пример:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  sayHello () {
    console. log(`Hello, I'm ${this.firstName}`)
  }
}
class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}
class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Теперь предположим, что вы хотите создать третий подкласс. Этот подкласс представляет собой смесь Designer и Developer — они могут проектировать и кодировать. Назовем его DesignerDeveloper (или DeveloperDesigner, как вам нравится).

Как бы вы создали третий подкласс?

Мы не можем расширять классы Designer и Developer одновременно. Это невозможно, потому что мы не можем решить, какие свойства будут первыми. Это часто называют проблемой алмаза (The Diamond Problem).

Проблема с алмазом может быть легко решена, если мы сделаем что-то вроде Object.assign — где мы будем отдавать приоритет одному объекту над другим. Если мы воспользуемся подходом Object.assign, мы сможем расширить такие классы. Но это не поддерживается в JavaScript.

// Doesn't work
class DesignerDeveloper extends Developer, Designer {
  // ...
}

Поэтому нам нужно полагаться на композицию.

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

На практике это может выглядеть так:

const skills = {
  code (thing) { /* ... */ },
  design (thing) { /* ... */ },
  sayHello () { /* ... */ }
}

В этом случае мы можем полностью пропустить Human и создать три разных класса в зависимости от их навыков.

Вот код для DesignerDeveloper:

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}
const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)

Вы можете сделать то же самое с Developer и Designer.

class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 
    Object.assign(this, {
      design: skills.design,
      sayHello: skills. sayHello
    }) 
  }
}
class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 
    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    }) 
  }
}

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

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})

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

Композиция с фабричными функциями

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

function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,    
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}
Наследование и Композиция одновременно

Никто не говорит, что нельзя использовать наследование и композицию одновременно. Мы можем!

Если взять пример, который мы до сих пор разобрали, Designer, Developer и DesignerDeveloper Human по-прежнему останется Human. Но подклассы будут наследоваться от Human.

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

class Human {
  constructor (firstName, lastName) {
    this. firstName = firstName
    this.lastName = lastName
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}
class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})

То же самое и с фабричными функциями:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () { 
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}
function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  }
}
Subclassing в реальном мире

И последнее, о подклассах и композиции. Несмотря на то, что эксперты отметили, что композиция более гибкая (и, следовательно, более полезная), Subclassing все же имеет свои достоинства. Многие вещи, которые мы используем сегодня, построены на стратегии Subclassing.

Например: событие click , которое мы знаем и любим, — это MouseEvent. MouseEvent — это подкласс UIEvent, который, в свою очередь, является подклассом Event.

Другой пример: элементы HTML — это подклассы Nodes. Вот почему они могут использовать все свойства и методы Nodes.

Предварительный вердикт

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

Далее мы рассмотрим классы и фабричные функции более подробно.


Классы и фабричные функции — инкапсуляция

Мы рассмотрели четыре различных варианта объектно-ориентированного программирования. Две из них — классы и фабричные функции — проще в использовании по сравнению с остальными.

Но остаются вопросы: что использовать? И почему?

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

  1. Наследование
  2. Инкапсуляция
  3. this

Мы только что говорили о наследовании. Теперь поговорим об инкапсуляции.

Инкапсуляция

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

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

Простая инкапсуляция

Самая простая форма инкапсуляции — это область видимости блока.

{
  // Variables declared here won't leak out
}

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

const food = 'Hamburger'
{
  console.log(food)
}

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

{
  const food = 'Hamburger'
}
console.log(food)

Примечание. Переменные, объявленные с помощью var, не учитывают область действия блока. Вот почему я рекомендую вам использовать let или const для объявления переменных.

Инкапсуляция с функциями

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

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()
Инкапсуляция и объектно-ориентированное программирование

Когда вы создаете объекты, вы хотите сделать некоторые свойства общедоступными (чтобы люди могли их использовать). Но вы также хотите сохранить некоторые свойства закрытыми (чтобы другие не могли нарушить вашу реализацию).

Давайте рассмотрим это на примере, чтобы прояснить ситуацию. Допустим, у нас есть 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, потому что они могут сломать машину.

Есть два способа запретить пользователям устанавливать топливо:

  1. По соглашению
  2. Использовать настоящие приватные переменные
По соглашению

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

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 для получения и установки топлива.

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
  }
}

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

Сначала вам нужно объявить частную переменную вне конструктора.

class Car {
  // Declares private variable
  #fuel 
  constructor () {
    // Use private variable
    this. #fuel = 50
  }
}

В этом случае мы можем использовать сокращение и заранее объявить #fuel, поскольку мы устанавливаем для топлива значение 50.

class Car {
  #fuel = 50
}

Вы не можете получить доступ к #fuel за пределами автомобиля. Вы получите сообщение об ошибке.

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

Примечание: лучше использовать геттеры и сеттеры вместо 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

Мы можем создать функции получения и установки для использования этой частной переменной 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.

Шесть контекстов:

  1. В глобальном контексте
  2. В конструкторе объекта
  3. В свойствах объекта
  4. В простых функциях
  5. В стрелочных функциях
  6. В event listener

Я подробно рассмотрел эти шесть контекстов тут. Прочтите эту статью, если вам нужна помощь в понимании this.

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

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

Еще не вернулся? Хороший. Пошли!

Использование
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 указывает на 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() 

То, что мы рассмотрели до сих пор, это был простой пример. Непросто решить, действительно ли это необходимо, пока мы не создадим достаточно сложный пример. Так что давай сделаем это.

Подробный пример

Допустим, у нас есть blueprint Human. Этот Human имеет свойства firstName и lastName и метод sayHello.

У нас есть blueprint Developer, созданный на основе Human. Developer могут кодировать, поэтому у них будет метод code. Developer также хотят объявить себя разработчиками, поэтому нам нужно перезаписать sayHello и добавить в консоль «Я разработчик».

Мы создадим этот пример с помощью классов и фабричных функций. (Мы сделаем пример с this и пример без this для фабричной функций).

Пример с классами

Во-первых, у нас есть blueprint Human . This Human имеет свойства firstName и lastName, а также метод sayHello.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName 
  }
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

У нас есть blueprint Developer, созданный на основе Human. Разработчики могут кодировать, поэтому у них будет метод code.

class Developer extends Human {
  code (thing) {
    console. log(`${this.firstName} coded ${thing}`)
  }
}

Разработчики также хотят объявить себя разработчиками. Нам нужно перезаписать sayHello и добавить в консоль «Я разработчик». Мы делаем это, вызывая метод SayHello для Human. Сделаем это с помощью super.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}
Пример с фабричными функциями (с
this)

Опять же, во-первых, у нас есть blueprint Human . Этот Human имеет свойства firstName и lastName, а также метод sayHello.

function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console. log(`Hello, I'm ${this.firstName}`)
    }
  }
}

Затем у нас есть Developer, созданный на основе Human. Разработчики могут кодировать, поэтому у них будет метод code.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Разработчики также хотят объявлять себя разработчиками. Нам нужно перезаписать sayHello и добавить в консоль «Я разработчик».
Мы делаем это, вызывая метод SayHello для Human. Мы можем сделать это на примере 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)

Вот полный код с использованием функций Factory (с 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 непосредственно в обоих схемах.

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, а фабричные функции — нет. Я предпочитаю использовать фабричные функции, потому что:

  1. Контекст this может измениться (что может сбивать с толку)
  2. Код, написанный с использованием фабричных функций, короче и чище (поскольку мы можем использовать инкапсулированные переменные, не используя this. #Variable).

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

Классы и фабричные функции — Event listeners

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

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

Но на самом деле это не так.

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

Создание счетчика

Далее мы построим простой счетчик. Мы используем все, что вы узнали из этой статьи, включая приватные переменные.

Скажем, счетчик содержит две вещи:

  1. Сам счетчик
  2. Кнопку для увеличения значения счетчика

Вот простейший HTML-код счетчика:

<div>
  <p>Count: <span>0</span>
  <button>Increase Count</button>
</div>
Создание счетчика с использованием классов
class Counter () {
  constructor (counter) {
    // Do stuff 
  } 
}
// Usage 
const counter = new Counter(document. querySelector('.counter'))

Нам нужно получить два элемента в классе Counter:

  1. <span> содержащий счетчик — нам нужно обновить этот элемент, когда счетчик увеличивается
  2. <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. К сожалению, мы не можем сразу использовать extensionCount в качестве обратного вызова. Если попробуем, то получим сообщение об ошибке.

class Counter () {
  // ...
  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }
  // Methods
}

Вы получаете сообщение об ошибке, потому что this указывает на buttonElement. (Это контекст прослушивателя событий.) Вы увидите buttonElement, если выведите this в консоль.

Нам нужно изменить значение this обратно на экземпляр для increaseCount , чтобы все работало. Сделать это можно двумя способами:

  1. Используя bind
  2. Используя стрелочную функцию

Большинство людей используют первый метод (но второй проще).

Добавление прослушивателя событий с
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 с помощью стрелочных функций. Если вы сделаете это, значение this для increaseCount будет сразу же привязано к значению экземпляра.

Итак, вот код, который вам нужен:

class Counter () {
  // . ..
  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }
  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }
  // ...
}
Полная версия кода

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

https://codepen.io/anon/embed/VwabbEE?height=450&theme-id=1&slug-hash=VwabbEE&default-tab=result

Создание счетчика с фабричными функциями

Здесь мы сделаем то же самое.

function Counter (counter) {
  // ...
}
const counter = Counter(document.querySelector('.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 было изменено. Вам потребуется то же самое, что и с классами — с функциями привязки или стрелочными функциями, чтобы снова заставить обработчик событий работать.

И это подводит меня ко второй проблеме.

Вторая уловка с
this

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

function Counter (counterElement) {
  // ...
  const counter = {
    // Do not do this. 
    // Doesn't work because `this` is `Window`
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}

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

Код

https://codepen.io/anon/embed/WNwjjaQ?height=450&theme-id=1&slug-hash=WNwjjaQ&default-tab=result

Вердикт для слушателей событий

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

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


Заключение

Мы говорили о четырех разновидностях объектно-ориентированного программирования:

  1. Использование функций конструктора
  2. Использование классов
  3. Объекты, связанные с другими объектами (OLOO)
  4. Использование фабричных функций

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

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

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

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

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

Я надеюсь, что это статья прольет свет на объектно-ориентированное программирование в JavaScript для вас. Если вам понравилась эта статья, возможно, вам понравится мой курс JavaScript, Learn JavaScript, где я объясню (почти) все, что вам нужно знать о JavaScript.

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

Была ли вам полезна эта статья?

[5 / 5]


Spread the love

Современная Front-End разработка.

Объектно-ориентированное программирование в JavaScript. (Лекция 4)

Похожие презентации:

Объектно-ориентированное программирование. Лекция 4

Объектно-ориентированное программирование

Объектно-ориентированное программирование (ООП)

Технология программирования. Объектно-ориентированное программирование

Введение в объектно-ориентированное программирование

Объектно-ориентированное программирование. Язык С++

JavaScript. Разработка мобильных приложений. (Лекция 8)

Объектно-ориентированное программирование (ООП)

Объектно-ориентированное программирование

Объектно-ориентированное программирование в Java

Современная
Front-End разработка
Объектно-ориентированное
программирование в
JavaScript
Павел Бекетов
[email protected]
Работа с DOM
Есть такой фрагмент html:
Из JavaScript кода мы имеем полный доступ к нему с помощью
глобального объекта document:
Работа с DOM
Из JS мы можем менять аттрибуты элементов:
Вешаем события на кнопку:
Важно: нельзя повесить несколько событий onclick на элемент,
они будут переопределяться.
DOM события
На элемент лучше вешать события с
помощью метода
addEventListener(type, eventListener),
порядок вызова слушателей
определяется их объявлением:
Удаляются события с помощью метода
removeEventListener(type, eventListener),
где eventListener – ранее привязанная
функция:
DOM события
В функцию eventListener передается аргумент event, содержащий
информацию по событию, а также ряд методов:
Фунцкия event.stopPropagation используется, чтобы остановить вызов
последующих слушателей
Поиск элементов в DOM
Для поиска элементов в DOM дереве можно пользоваться функциями
поиска:
document.getElementById(id) – возвращает элемент с заданным id или
null, если элемент не найден
document.getElementsByClassName(class) – возвращает массив
элементов с заданным именем класса, можно использовать несколько
имен:
document.getElementsByClassName(‘red test’) – вернет все элементы с
классами “red” и “test”
document. querySelector(selectors) – возвращает первый попавшийся
элемент, удовлетворяющий селектору, либо null и
document.querySelectorAll(selectors) – возвращает массив,
удовлетворяющих элементов:
document.querySelectorAll(“.red.test”) — результат аналогичен
предыдущему примеру с className
Изменение и добавление элементов в DOM
Создание новых элементов:
Клонирование элементов:
NOTES:
Клонированные элементы не добавляются в DOM, чтобы это сделать,
используется appendChild()
Свойства добавленные через JS-код(например onclick) не копируются
AJAX
Объект XMLHttpRequest (или, как его кратко называют, «XHR») дает
возможность из JavaScript делать HTTP-запросы к серверу без
перезагрузки страницы.
AJAX
Предыдущий вариант использования делает синхронный запрос, весь
JavaScript «подвиснет», пока запрос не завершится, поэтому почти всегда
используется асинхронная версия XHR:
AJAX
Состояния запроса(readyState):
0; // начальное состояние
1; // вызван open
2; // получены заголовки
3; // загружается тело (получен очередной пакет данных)
4; // запрос завершён
Работа с заголовками:
setRequestHeader(name, value) – задает заголовок запроса с именем
name
getResponseHeader(name) – возвращает заголовок ответа по имени
getAllResponseHeaders() — возвращает все заголовки ответа
Наследование в JavaScript
На уровне языка реализовано наследование на прототипах. С помощью
некоторых трюков можно сделать наследование на классах, объявить
приватные свойства объекта и многое другое.
Создание объекта. Функция-конструктор
Любая функция может создать объект:
Класс объекта определяется функцией, которая его создала. Для проверки
принадлежности классу есть оператор instanceof:
alert(animal instanceof Animal) // => true
Наследование через прототип
Реализуется наследование через неявную(внутреннюю) ссылку одного
объекта на другой, который называется его прототипом и в спецификации
обозначается [[prototype]]. Это свойство обычно скрыто от программиста.
Также существует свойство с похожим названием prototype (без квадратных
скобок) — оно вспомогательное и указывает, откуда брать прототип при
создании объекта.
Когда вы ставите функции Animal свойство Animal.prototype = XXX — вы этим
декларируете: «все новые объекты класса Animal будут иметь прототип
XXX».
Ссылка [[prototype]] работает так:
1)Любое запрошенное свойство
ищется сначала в rabbit
2)Если свойство там не найдено, то
оно ищется в rabbit. [[prototype]], т.е
в animal
Наследование через прототип
Расширяем прототип функции-конструктора:
Наследование на классах. Функция extend
В качестве решения для полноценного наследования, используется
функция extend, которая является почти стандартом:
Вызов родительских методов
С помощью функции extend легко получить доступ к родительским
методам:
Вызов родительских методов
Обязательно следить за контекстом, в данном случае использование this
вместо Rabbit приведет к ошибке:
Использование прототипов для расширения
функционала
Задача: нужно логировать время старта и время
окончания AJAX запросов по URL.
Домашнее задание
Примечание: для работы с файлами нужно запустить браузер со
специальным параметром например для chromium:
chromium —allow-file-access-from-files
Задание
Cоздаем два файла: posts.json и users.json в корне проекта и
заполняем их тестовыми данными, модели:
Домашнее задание
Необходимо обернуть XMLHttpRequest в сервис httpService,
который экпортирует метод get(url, callback). Пример
использования:
Реализовать модуль userService. Экпортирует фукнцию getUserById,
которая возвращает id
Создать функцию конструктор Post, интерфейс:
Домашнее задание
Используя созданные классы, вывести содержимое файла
posts.json на странице Ленты, т.е. на каждую запись в файле
выводить отдельный пост.
Вопросы?

English     Русский Правила

Объектно-ориентированное программирование — Изучите веб-разработку

  • Предыдущий
  • Обзор: Объекты
  • Следующий

Объектно-ориентированное программирование (ООП) — это парадигма программирования, фундаментальная для многих языков программирования, включая Java и C++. В этой статье мы представим обзор основных концепций ООП. Мы опишем три основных понятия: классы и экземпляры , наследование и инкапсуляция . Пока мы будем описывать эти понятия без привязки конкретно к JavaScript, поэтому все примеры даны в псевдокоде.

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

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

Необходимые условия: Понимание функций JavaScript, знакомство с основами JavaScript (видеть Первые шаги и Стандартные блоки) и основы OOJS (см. Введение в объекты и прототипы объектов).
Цель: Чтобы понять основные концепции объектно-ориентированного программирования на основе классов.

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

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

Итак, Профессор может быть классом в нашей системе. В определении класса перечислены данные и методы, которые есть у каждого профессора.

В псевдокоде класс Professor может быть записан так:

 class Professor
    характеристики
        имя
        учит
    методы
        класс (бумага)
        представить себя ()
 

Это определяет класс Professor с:

  • двумя свойствами данных: name и обучают
  • два метода: grade() для оценки статьи и IntroductionSelf() для представления себя.

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

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

 class Professor
    характеристики
        имя
        учит
    конструктор
        Профессор (имя, преподает)
    методы
        класс (бумага)
        представить себя ()
 

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

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

 walsh = новый профессор («Уолш», «Психология»)
lillian = новый профессор («Лилиан», «Поэзия»)
walsh.teaches // 'Психология'
walsh.introduceSelf() // 'Меня зовут профессор Уолш, и я буду вашим профессором психологии.'
лилиан.преподает // 'Поэзия'
lillian.introduceSelf() // 'Меня зовут профессор Лилиан, и я буду вашим профессором поэзии.'
 

Это создает два объекта, оба экземпляра класса Professor .

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

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

 класс Студент
    характеристики
        имя
        год
    конструктор
        Студент (имя, год)
    методы
        представить себя ()
 

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

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

 class Person
    характеристики
        имя
    конструктор
        Человек(имя)
    методы
        представить себя ()
класс Профессор: расширяет человека
    характеристики
        учит
    конструктор
        Профессор (имя, преподает)
    методы
        класс (бумага)
        представить себя ()
класс Student : расширяет человека
    характеристики
        год
    конструктор
        Студент (имя, год)
    методы
        представить себя ()
 

В этом случае мы бы сказали, что Лицо является суперклассом или родительским классом как Профессора , так и Студента . И наоборот, Профессор и Студент являются подклассами или дочерними классами Человека .

Вы могли заметить, что IntroductionSelf() определен во всех трех классах. Причина этого в том, что, хотя все люди хотят представиться, они делают это по-разному:

 walsh = новый профессор («Уолш», «Психология»)
walsh.introduceSelf() // 'Меня зовут профессор Уолш, и я буду вашим профессором психологии.'
лето = новый студент('Лето', 1)
summers.introduceSelf() // 'Меня зовут Саммерс, я учусь на первом курсе.'
 

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

 pratt = new Person('Pratt')
pratt.introduceSelf() // 'Меня зовут Пратт.'
 

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

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

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

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

 если (студент.год > 1) {
    // впускаем ученика в класс
}
 

Проблема в том, что если мы решим изменить критерии, разрешающие учащимся изучать стрельбу из лука, например, потребовав разрешения от родителей или опекунов, нам потребуется обновить каждое место в нашей системе, выполняющее этот тест. Было бы лучше иметь метод canStudyArchery() для объектов Student , который реализует логику в одном месте:

 class Student : extends Person
    характеристики
       год
    конструктор
        Студент (имя, год)
    методы
       представить себя ()
       canStudyArchery() { вернуть этот год > 1 }
 
 если (student.canStudyArchery()) {
    // впускаем ученика в класс
}
 

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

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

 класс Студент: расширяет человека
    характеристики
       частный год
    конструктор
        Студент (имя, год)
    методы
       представить себя ()
       canStudyArchery() { вернуть этот год > 1 }
студент = новый студент('Вебер', 1)
student.year // ошибка: 'year' является частной собственностью Student
 

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

В этой статье мы описали некоторые основные возможности объектно-ориентированного программирования на основе классов, реализованные в таких языках, как Java и C++.

В двух предыдущих статьях мы рассмотрели пару основных функций JavaScript: конструкторы и прототипы. Эти функции, безусловно, имеют некоторое отношение к некоторым концепциям ООП, описанным выше.

  • конструкторы в JavaScript предоставляют нам что-то вроде определения класса, позволяя нам определять «форму» объекта, включая любые содержащиеся в нем методы, в одном месте. Но и здесь можно использовать прототипы. Например, если метод определен в конструкторе прототип , то все объекты, созданные с помощью этого конструктора, получают этот метод через свой прототип, и нам не нужно определять его в конструкторе.
  • Цепочка прототипов кажется естественным способом реализации наследования. Например, если у нас может быть объект Student , прототипом которого является Person , то он может наследовать name и переопределить IntroductionSelf() .

Но стоит понимать отличия этих возможностей от "классических" концепций ООП, описанных выше. Мы выделим пару из них здесь.

Во-первых, в ООП на основе классов классы и объекты являются двумя отдельными конструкциями, а объекты всегда создаются как экземпляры классов. Кроме того, существует различие между функцией, используемой для определения класса (сам синтаксис класса), и функцией, используемой для создания экземпляра объекта (конструктор). В JavaScript мы можем и часто создаем объекты без какого-либо отдельного определения класса, используя либо функцию, либо объектный литерал. Это может сделать работу с объектами намного более легкой, чем в классическом ООП.

Во-вторых, хотя цепочка прототипов выглядит как иерархия наследования и в некоторых отношениях ведет себя аналогично, в других она отличается. Когда создается экземпляр подкласса, создается один объект, который объединяет свойства, определенные в подклассе, со свойствами, определенными выше по иерархии. При прототипировании каждый уровень иерархии представляется отдельным объектом, и они связаны друг с другом через свойство __proto__ . Поведение цепочки прототипов меньше похоже на наследование и больше похоже на делегация . Делегирование — это шаблон программирования, при котором объект, когда его просят выполнить задачу, может выполнить задачу сам или попросить другой объект (его делегат ) выполнить задачу от его имени. Во многих отношениях делегирование является более гибким способом объединения объектов, чем наследование (во-первых, можно изменить или полностью заменить делегата во время выполнения).

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

В этой статье описаны основные особенности объектно-ориентированного программирования на основе классов и кратко рассмотрено сравнение конструкторов и прототипов JavaScript с этими концепциями.

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

  • Предыдущий
  • Обзор: Объекты
  • Следующий
  • Основы объекта
  • Прототипы объектов
  • Концепции объектно-ориентированного программирования
  • Классы в JavaScript
  • Работа с данными JSON
  • Практика строительства объектов
  • Добавление функций в нашу демонстрацию прыгающих мячей

Последнее изменение: , участниками MDN

Объектно-ориентированное программирование на JavaScript | Код с Mosh

 

Укрепите свое понимание JavaScript и подготовьтесь к техническим собеседованиям

Станьте лучшим разработчиком JavaScript

Слышали об объектно-ориентированном программировании, но не знаете, что это такое?

Проще говоря, объектно-ориентированное программирование (ООП) — популярный стиль программирования.

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

Если вы ищете курс, который научит вас ООП с нуля без каких-либо жаргонов и чепухи, этот курс для вас.

Идеальное сочетание теории и практики с реальными примерами, упражнениями и пошаговыми решениями.

  • Средний
  • Пожизненный доступ
  • 60 уроков
  • 4 часа видео
  • Упражнения и решения
  • Загружаемый
  • Субтитры
  • Доступ на мобильном телефоне/телевидении
  • Сертификат

Привет! Меня зовут Мош, и я хочу демистифицировать для вас объектно-ориентированное программирование!

К концу этого курса вы сможете…

  • Укрепить свое понимание JavaScript
  • Уверенно используйте сложные функции JavaScript
  • Управление и снижение сложности кода
  • Создание повторно используемых блоков кода
  • Подготовка к JavaScript-интервью
  • Написать чистый код

Чему вы научитесь.

..
  • Принципы объектно-ориентированного программирования
  • Как реализовать концепции ООП в JavaScript
  • Все об объектах, их свойствах, дескрипторах свойств, геттерах и сеттерах
  • Заводские и конструкторские функции
  • Как «это» работает в JavaScript
  • Прототипы и наследование прототипов: одна из запутанных частей JavaScript
  • Возможности ES6+, используемые в объектно-ориентированном программировании
  • И многое, многое другое!

Небольшие, запоминающиеся и легко усваиваемые видеоролики без лишних слов

Для кого этот курс?

  • Любой, кто хочет поднять свои навыки JavaScript на новый уровень
  • Любой, кто хочет изучить и понять принципы объектно-ориентированного программирования
  • Любой, кто хочет изучить методы применения объектно-ориентированных концепций в JavaScript
  • Любой, кто готовится к собеседованию по программированию на JavaScript

Предварительные требования

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

Присоединяйтесь к 68978 счастливым ученикам!

"Привет, Мош, мне очень нравится твой курс ООП. Я наконец-то понимаю прототипное наследование и асинхронное программирование! Я уже 8 месяцев учусь, как стать разработчиком, и делаю очень хорошие успехи. Моя предыдущая инструкция перед твоим курсы по этим темам не были достаточно подробными, чтобы быть особенно полезными. Вы действительно вбиваете в курс дела детали о функциях-конструкторах и использовании свойства .prototype ".

- Брайан Халлер

"После завершения этого курса по объектно-ориентированному программированию с помощью JavaScript я чувствую себя намного лучше подготовленным к работе, в которой я использую JavaScript в шаблоне MVC. Мош смог четко объяснить код, который он пишет, чтобы новички могли его понять. Я благодаря его лекциям я могу писать более чистый и красноречивый код, и мне очень понравилось проводить время с этим курсом. Я рад называть Моша учителем».

- Мэтью Хемингуэй

"Мош проделывает большую работу по освещению множества вариантов ООП в JS. Он доскональный, но доступный, и мне очень понравилось его тщательное рассмотрение каждой концепции по мере продвижения."

- Шон Ларкин

Ваш инструктор


Мош Хамедани

Привет! Меня зовут Мош Хамедани. Я инженер-программист с двадцатилетним стажем. Благодаря своим онлайн-курсам и каналу на YouTube я научил миллионы людей программировать и стать профессиональными программистами.

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


Учебная программа курса


Начало работы (17м)

Доступно в дней

дней после регистрации

  • Предварительный просмотр

    1- Что такое ООП (1:45)
  • Предварительный просмотр

    2- Четыре столпа ООП (7:02)
  • Предварительный просмотр

    3- Настройка среды разработки (2:16)
  • Старт

    4- Структура курса (2:55)
  • Старт

    5- Следуй за мной
  • Старт

    6- Пути обучения

Объекты (1ч25м)

Доступно в дней

дней после регистрации

  • Старт

    1. Введение (0:44)
  • Старт

    2- Литералы объекта (3:03)
  • Старт

    3- Фабрики (2:51)
  • Старт

    4- Конструкторы (5:36)
  • Старт

    5- Свойство конструктора (2:24)
  • Старт

    6. Функции — это объекты (4:47)
  • Старт

    7- Значение против ссылочных типов (5:49)
  • Старт

    8- Добавление или удаление свойств (3:53)
  • Старт

    9- Перечисление свойств (2:50)
  • Старт

    10- Абстракция (4:02)
  • Старт

    11- Частные свойства и методы (4:05)
  • Старт

    12- Геттеры и сеттеры (5:36)
  • Старт

    13- Шпаргалка
  • Старт

    14- Упражнение- Секундомер (2:08)
  • Старт

    15- Решение- Секундомер (2:31)

Прототипы (40м)

Доступно в дней

дней после регистрации

  • Старт

    1- Наследование (2:26)
  • Старт

    2- Прототипы и прототипное наследование (5:33)
  • Старт

    3- Многоуровневое наследование (2:51)
  • Старт

    4- Дескрипторы свойств (5:07)
  • Старт

    5- Прототипы конструктора (3:52)
  • Старт

    6- Прототип против членов экземпляра (6:04)
  • Старт

    7- Итерация членов экземпляра и прототипа (2:39)
  • Старт

    8- Избегайте расширения встроенных объектов (1:41)
  • Старт

    9- Шпаргалка
  • Старт

    10- Упражнение (1:33)
  • Старт

    11- Решение (5:38)

Прототип наследства (50 м)

Доступно в дней

дней после регистрации

  • Старт

    1- Создание собственного прототипа наследования (5:34)
  • Старт

    2- Сброс конструктора (4:00)
  • Старт

    3- Вызов суперконструктора (3:48)
  • Старт

    4- Наследование промежуточной функции (3:05)
  • Старт

    5- Переопределение метода (3:29)
  • Старт

    6- Полиморфизм (4:22)
  • Старт

    7- Когда использовать наследование (3:23)
  • Старт

    8- Миксины (5:53)
  • Старт

    9- Шпаргалка
  • Старт

    10. Упражнение. Наследование прототипов (3:18)
  • Старт

    11- Решение - Прототипное наследование (5:53)
  • Старт

    12- Упражнение- Полиморфизм (2:35)
  • Старт

    13- Решение- Полиморфизм (5:11)

Классы ES6 (55 м)

Доступно в дней

дней после регистрации

  • Старт

    1- Классы ES6 (5:41)
  • Старт

    2- Подъем (3:46)
  • Старт

    3- Статические методы (4:17)
  • Старт

    4- Это ключевое слово (4:57)
  • Старт

    5- Частные члены, использующие символы (7:48)
  • Старт

    6- Частные участники, использующие WeakMaps (6:58)
  • Старт

    7- Геттеры и сеттеры (2:50)
  • Старт

    8- Наследование (4:00)
  • Старт

    9- Переопределение метода (2:33)
  • Старт

    10- Шпаргалка
  • Старт

    11- Упражнение (3:25)
  • Старт

    12- Решение (4:20)

Инструменты ES6 (30 м)

Доступно в дней

дней после регистрации

  • Старт

    1- Модули (3:42)
  • Старт

    2- Модули CommonJS (5:51)
  • Старт

    3- Модули ES6 (3:23)
  • Старт

    4- Инструменты ES6 (1:26)
  • Старт

    5- Вавилон (7:11)
  • Старт

    6- Вебпак (8:26)
  • Старт

    7- Шпаргалка
  • Старт

    8- Что изучать дальше

Часто задаваемые вопросы

Когда курс начинается и заканчивается?

Курс начинается сейчас и никогда не заканчивается! Это полностью самостоятельный онлайн-курс — вы сами решаете, когда начать и когда закончить.

Как долго у меня есть доступ к курсу?

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

Что делать, если курс меня не устраивает?

Мы никогда не хотим, чтобы вы были несчастны! Если вы не удовлетворены своей покупкой, свяжитесь с нами в течение первых 30 дней, и мы полностью вернем вам деньги.

Попробуйте без риска

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

Поднимите свои навыки JavaScript на новый уровень

Вот что вы получите, зарегистрировавшись.

  • 4 часа HD-видео
  • 60 уроков
  • Упражнения и решения
  • Малоизвестные советы экспертов
  • Рекомендации по применению
  • Распространенные ошибки, которых следует избегать
  • Бессрочный доступ
  • Загружаемый исходный код
  • Учитесь в своем собственном темпе
  • Смотреть онлайн или офлайн
  • Смотреть на любом устройстве
  • Сертификат об окончании

Присоединяйтесь к 68978 счастливым ученикам!

Хватит тратить время на перескакивание с одного обучающего видео на другое.

С нашими замечательными отзывами и гарантией возврата денег, чего же вы ждете?

19 долларов 59 долларов Ограниченное время по этой цене

Купить Сейчас

29 долларов в месяц Отменить в любое время!

Подписаться & Сохранять Самый популярный

Учебник по веб-разработке

23 мая 2019 г. — чтение 10 мин.

Обучающий

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

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

Здесь вы изучите основы объектно-ориентированного JavaScript в ES5 и ES6, чтобы увидеть сравнения и то, как JavaScript стремится к стилю ООП.

Сегодня мы рассмотрим:

  • Что такое ООП (объектно-ориентированное программирование)?
  • ООП в JavaScript (ES5)
  • Объекты в JavaScript
  • Доступ к свойствам объекта
  • Функции как объекты
  • Объектно-ориентированный JavaScript в ES6
  • Что узнать дальше

Поднимите свой код JavaScript на новый уровень

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

Изучение ООП в JavaScript

Что такое ООП (объектно-ориентированное программирование)?

Если вы знакомы с другими языками, такими как C# и Java, то вы, вероятно, слышали термин объектно-ориентированное программирование (ООП).

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

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

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


ООП в JavaScript (ES5)

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

Вы, наверное, видели, что в других языках, таких как C++, Java и C#, для определения класса используется ключевое слово class. Класс имеет свойства и методы для каждого экземпляра этого класса. В этом случае класс действует как план объекта.

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

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

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

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

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


Объекты в JavaScript

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

Что такое объект?

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

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

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

  • Значения данных
  • Функции

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

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

Как создать литерал объекта

Литерал объекта можно создать:

  • Используя фигурные скобки {...} в объявлении.
  • Использование нового ключевого слова.
  • На основе существующего объекта с использованием метода create() .

Все эти подходы делают одно и то же. Вот как выглядит синтаксис:

Использование фигурных скобок

 var objectName = {
 
 // свойства определены
 имя_свойства1 : значение_свойства1,
 имя_свойства2 : значение_свойства2,
 имя_функции() {}
 
} 

Используя ключевое слово new

 var objectName = new Object() 

Используя метод create()

 var newObjectName = Object.create(existing)(existing)
 

Полезные ключевые слова: Get, Set, This

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

Set: Синтаксис set связывает свойство объекта с функцией, которая будет вызываться при попытке установить это свойство.

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


Продолжайте учиться.

Изучите концепции ООП в JavaScript, не просматривая видео или документацию. Текстовые курсы Educative легко просматриваются и включают живую среду кодирования, что делает обучение быстрым и эффективным.

Изучение объектно-ориентированного программирования на JavaScript



Доступ к свойствам объекта

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


Оператор точки

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

Здесь мы можем увидеть синтаксис оператора точки:

 имя_объекта.Имя_функции() 

Вот пример доступа к свойствам с помощью оператора точки:

 Вот пример доступа к свойствам с помощью оператора точки:
//создание объекта с именем shape
переменная форма = {
 //определение свойств объекта
 // установка значений данных
 название: «квадрат»,
 стороны : 4
}
//доступ к свойствам с помощью оператора точки
console.log("Name is:", shape.name) // использование оператора точки для доступа к "name"
console.log("Количество сторон:", shape.sides) //использование оператора точки для доступа к сторонам 

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

Другой способ доступа к значениям — использование квадратных скобок [ ] . Имя свойства, к которому осуществляется доступ, записывается в квадратных скобках в виде строки. Использование квадратных скобок также полезно для установки и удаления свойств.

Здесь мы видим синтаксис метода квадратных скобок:

 имя_объекта['имя_функции']() 

Вот пример доступа к свойствам с помощью квадратных скобок:

 //создание объекта с именем shape
переменная форма = {
 //определение свойств объекта
 // установка значений данных
 название: «квадрат»,
 стороны : 4
}
//доступ к свойствам с помощью квадратных скобок
console.log("Имя:", shape['name']) // для доступа к "name"
console.log("Количество сторон:", shape['sides']) // для доступа к "сторонам" 

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

Функции-конструкторы

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

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

Вот синтаксис реализации функции-конструктора:

 function ИмяФункции(параметр1, параметр2,...){
   //здесь инициализируются все свойства объекта
   // здесь определяются функции, которые будут предоставляться объектами
} 

Как видно сверху:

  • Ключевое слово function используется для определения функции.
  • Имя функции-конструктора должно быть написано с заглавной буквы, как и FunctionName в приведенном выше фрагменте.
  • Тело этой функции в основном представляет собой часть конструктора функции, поскольку оно инициализирует свойства, устанавливая их равными соответствующим параметрам, передаваемым в функцию.

Вот пример функции-конструктора:

 function Employee(_name, _age, _designation){
  это.имя = _имя
  this.age = _возраст
  это.обозначение = _обозначение
} 

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


Объекты-прототипы

Объекты-прототипы — это более простой способ добавления новых методов/свойств в функцию-конструктор.

Помимо свойств, которые вы создаете, существует дополнительное скрытое свойство, известное как свойство [[Prototype]] , которое присутствует внутри каждого объекта, созданного из функции-конструктора. Свойство прототипа либо указывает на другой объект, либо имеет значение null. Вот пример использования свойства Prototype:

 //Форма объекта
переменная форма={
 имя: «Прямоугольник»,
 стороны: 4
}
//Прямоугольный объект
вар Прямоугольник = {
 длина: 3,
 ширина: 5
}
//установка [[Prototype]] прямоугольника равным Shape
Прямоугольник. __proto__ = Форма
//создание экземпляра объекта с помощью Shape и Rectangle
console.log("Имя фигуры:",Rectangle.name)
console.log("Количество сторон",Rectangle.sides)
console.log("Длина:",Rectangle.length)
console.log("Ширина:",Rectangle.width) 

Здесь мы видим, что когда свойство прототипа Rectangle установлено в Shape, он может получить доступ ко всем свойствам в Shape. Если свойство не найдено в объекте, например, свойство name не найдено в Rectangle, JavaScript автоматически возьмет его из прототипа этого объекта, Shape. Это известно как прототипное наследование, где строки 20 и 21 известны как унаследованные свойства; это основано на концепции цепочки прототипов.


Объектно-ориентированный JavaScript в ES6

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

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


Объявление класса в JavaScript ES6

Синтаксис следующий:

 class ClassName {
  конструктор () {
    // инициализация свойств класса
  }
  //определены методы класса
} 

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


Создание экземпляра объекта из класса

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

 //создание класса с именем работник
сотрудник класса {
 //создание функции-конструктора
 конструктор(имя,возраст,обозначение){
   //все свойства определены так, как они были в функции-конструкторе
   это. имя = имя
   this.age = возраст
   this.обозначение = обозначение
   this.displayName = функция () {
     console.log("Имя:",this.name)
   }
 }
}
//создание экземпляра объекта с именем "employeeObj"
var employeeObj = новый сотрудник («Джо», 22 года, «Разработчик»)
//отображение свойств employeeObj
employeeObj.displayName()
console.log("Возраст",employeeObj.age)
console.log("Обозначение:",employeeObj.designation) 

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


Определение методов в классе

Всякий раз, когда метод объявляется внутри класса, он определяется в прототипе этого класса. Это означает, что всякий раз, когда экземпляр объекта обращается к нему, он берется из прототипа соответствующего класса.

Вот пример:

 //создание класса с именем employee
сотрудник класса {
 //создание функции-конструктора
 конструктор(имя,возраст,обозначение){
   //все свойства определены так, как они были в функции-конструкторе
   это.имя = имя
   this.age = возраст
   this.обозначение = обозначение
   this.displayName = функция () {
     console.log("Имя:",this.name)
   }
 }
 //определение методов в классе
 //метод getAge, возвращающий возраст текущего объекта
 получить возраст () {
   вернуть этот возраст
 }
} 

Вот что происходит в приведенном выше коде:

  • Функция getAge определяется вне функции-конструктора в строке 15.
  • Все такие методы хранятся в объекте-прототипе сотрудника.
  • Итак, новый объект, такой как employeeObj , имеет доступ ко всем методам, определенным в классе.
  • При вызове employeeObj метод getAge берется из employee.prototype.

Что предстоит узнать дальше

Хотя JavaScript нельзя считать языком ООП, использование версии ES6 (из-за использования классов) даст вам представление о том, что значит кодировать на более традиционном языке программирования ООП, таком как как С/С++. Основное различие между ES5 и ES6 заключается в добавлении и очистке синтаксиса.

Еще многое предстоит узнать, например:

  • Статические методы
  • Защитные свойства
  • Инкапсуляция данных
  • Миксины
  • и более

Если вам интересно углубиться в детали, вы можете изучить все основы с помощью Learn OOP in JavaScript . Вы перейдете к более продвинутым концепциям, таким как прототипное наследование, цепочка прототипов, переопределение методов и примеси.


Продолжить чтение о JavaScript и ООП

  • Путь к тому, чтобы стать фронтенд-разработчиком
  • Рекомендации по обращению строки в JavaScript, C++ и Python
  • 7 структур данных JavaScript, которые вы должны знать

НАПИСАЛ BYEducative

Присоединяйтесь к сообществу, насчитывающему более 1,4 миллиона читателей. Бесплатное электронное письмо раз в два месяца с обзором лучших статей и советов по программированию на сайте Educative.

Изучение основ объектно-ориентированного программирования на JavaScript

Содержание

Введение

  • 00:00:00 - 00:06:12

    Введение в курс

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

  • 00:06:13 - 00:11:59

    Объектно-ориентированная парадигма

    Уилл дает обзор того, что будет преподаваться, а затем отвечает, почему парадигма или способ организации кода вообще существует.

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

  • 00:12:00 - 00:14:56

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

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

  • 00:14:57 - 00:16:47

    Обозначения с точкой объекта

    Представлен основной способ присвоения данных существующему объекту.

  • 00:16:48 - 00:19:25

    Object.create

    Уилл использует Object. create, чтобы начать разговор о принципе DRY.

  • 00:19:26 - 00:31:28

    Создание объектов с функциями

    Будут представлены решения 1, функция, которая возвращает объект при запуске. Затем объясняется, почему решение не будет работать на практике.

Прототип и новый

  • 00:31:29 - 00:33:52

    Избегайте дублирования с помощью прототипа .create для создания объектов, но по-прежнему имеет доступ к функциональным возможностям.

  • 00:33:53 - 00:41:31

    Пошаговое руководство по прототипу

    Будут представлены схемы решения 2 с использованием Object.create и цепочки прототипов с участием аудитории.

  • 00:41:32 - 00:52:25

    Цепочка прототипов

    Уилл отслеживает изображенное на диаграмме решение, используя increment() для экземпляра user1, демонстрируя, как JavaScript обращается к функции. Уроки извлекаются из решения, которое влияет на то, как мы рассматриваем объектно-ориентированное программирование в JavaScript. он автоматизирует создание объекта, а также то, как он создает необходимость ссылаться на него.

  • 00:55:35 - 01:02:33

    Функции — это объекты и функции

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

  • 01:02:34 - 01:19:06

    новые функции ключевого слова и общего доступа с прототипом

    Будут представлены диаграммы решения 3 с использованием нового ключевого слова и цепочки прототипов с участием аудитории.

  • 01:19:07 - 01:26:43

    Обзор новых

    Уилл рассказывает о преимуществах и недостатках решения 3, а темы, затронутые позже, предвосхищаются.

Scope & this

  • 01:26:44 - 01:35:43

    Вызов методов прототипа

    Рефакторинг решения 3 для включения неявного параметра this. По мере того, как решение изображается на диаграмме, внимание уделяется тому, когда это полезно и как в игру вступает масштаб.

  • 01:35:44 - 01:43:03

    this Проблемы определения области действия ключевого слова

    Функция UserCreator.prototype.increment подвергается дальнейшему рефакторингу, что создает интересную проблему с областью действия.

  • 01:43:04 - 01:48:42

    Решение области видимости с помощью стрелочных функций

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

  • 01:48:43 - 01:56:25

    Класс ES6 Ключевое слово

    WIll представляет, как JavaScript реализует синтаксический сахар класса.

  • 01:56:26 - 02:01:07

    Резюме ключевого слова класса

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

Цепочка прототипов по умолчанию

  • 02:01:08 - 02:09:14

    Объекты по умолчанию __proto__

    Уилл использует пример для дальнейшего объяснения того, как __proto__ встроен в каждый объект JavaScript. В дополнение к __proto__, hasOwnProperty — это встроенный метод, обладающий дополнительной функциональностью.

  • 02:09:15 - 02:20:29

    Function.prototype и Array.prototype

    Будет показана цепочка прототипов при вызове метода функции.

  • 02:20:30 - 02:23:18

    Парное программирование OOJS

    Уилл направляет учащихся к практическим задачам и устанавливает парное программирование.

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

  • 02:23:19 - 02:28:08

    Введение в подклассы и наследование

    Уилл представляет наследование как способ создания подклассов в JavaScript и объясняет, почему термин «наследование» вводит в заблуждение.

  • 02:28:09 - 02:41:38

    Создать объект с фабричной функцией

    Решение 3 дополнительно переработано для реализации классов. Уилл проходит код вместе со зрителями.

  • 02:41:39 - 02:46:45

    Создание функции подфабрики

    Уилл устанавливает следующий фрагмент кода и объясняет, как этот код взаимодействует с кодом, написанным в предыдущем сегменте . Object.setPrototypeOf используется для установки __proto__ функции для достижения необходимой нам функциональности.

  • 02:46:46 - 02:58:40

    Создание объекта с функцией подфабрики

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

  • 02:58:41 - 03:01:49

    Поиск прототипа

    Уилл завершает это решение диаграммой поиска прототипа с помощью объекта, экземпляр которого был создан ранее.

  • 03:01:50 - 03:06:44

    Обзор подкласса

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

  • 03:06:45 - 03:15:29

    Вызов и применение

    Уилл демонстрирует, как вручную установить значение ключевого слова this внутри функции.

Подкласс с новым и вызовом

  • 03:15:30 - 03:25:35

    Создайте объект с новым

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

  • 03:25:36 - 03:32:19

    Создание подкласса с помощью конструктора

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

  • 03:32:20 - 03:43:42

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

    Передавая наш объект newPaidUser ("this") в userCreator, Уилл демонстрирует, как this может ссылаться на контекст, который находится на один уровень выше текущей области.

  • 03:43:43 - 03:49:37

    Назначение свойств экземпляру

    Уилл показывает, как создание экземпляра объекта платного пользователя с использованием ключевого слова new создает новый контекст для рассмотрения.

  • 03:49:38 - 03:52:46

    Трассировка прототипа

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

Создание подклассов с помощью class, extends и super

  • 03:52:47 - 04:01:14

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

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

  • 04:01:15 - 04:10:29

    Создание подкласса с расширениями

    Will представляет расширения и диаграммы того, что они предлагают для рефакторинга кода.

  • 04:10:30 - 04:18:03

    Создание объекта с подклассом

    Создается экземпляр последнего контекста выполнения курса, и с помощью ключевого слова new создается платный пользователь. Уилл также кратко описывает функцию Reflect.construct(), которая действует как ключевое слово new, но как функция.

  • 04:18:04 - 04:28:36

    Использование super в конструкторе подкласса

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

Заключение

  • 04:28:37 - 04:30:27

    Подведение итогов

    Уилл завершает курс рассуждениями о том, почему важно понимать, что происходит под капотом в JavaScript.

Пришло время поговорить об объектно-ориентированном JavaScript

JavaScript

Афинья Дешалерт

• 5 минут чтения

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

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

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

JavaScript повсюду. С JavaScript легко замарать руки и сделать с его помощью что-то полезное. Но с легкостью приходят потенциальные долгосрочные проблемы. Многие программисты JavaScript не обучены методам объектно-ориентированного программирования. Это не их вина, нет. Иногда мы просто увязаем в попытках заставить что-то работать. Иногда мы просто не знаем, чего мы не знаем.

Объектно-ориентированное программирование основано на другом менталитете. Идея заключается в том, что вы создаете план того, как выглядит ваш «объект», и вызываете его снова и снова, чтобы делать с ним все, что хотите.

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

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

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

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

Концептуально так выглядит класс

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

Начало спагетти код

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

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

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

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

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

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

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

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

Я часто думаю об объектно-ориентированном программировании как об активном решении ограничить и ограждать рамки того, над чем я работаю. Angular требует общего понимания фреймворка для реализации ООП — я думаю, что мне нужно будет сделать это в отдельной статье в будущем.

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

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

Бесплатный продвинутый курс JavaScript | Rithm School

Цели

К концу этой главы вы должны уметь:

  • Описывать ООП, не говоря о коде
  • Определить инкапсуляцию
  • Определить абстракцию
  • Определить наследование

Что такое объектно-ориентированное программирование?

Объектно-ориентированное программирование — это метод программирования, который пытается смоделировать некоторый процесс или вещь в мире как класс или объект . Объект в этом случае не совпадает с версией объекта JavaScript. Вместо этого вы можете концептуально думать о классе или объекте как о чем-то, что имеет данные и может выполнять операции с этими данными. Цель объектно-ориентированного программирования состоит в том, чтобы инкапсулировать ваш код в логические группы с использованием классов, чтобы вы могли рассуждать о своем коде на более высоком уровне. Давайте рассмотрим пример:

Покер Пример

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

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

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

  • Карта
  • Колода карт
  • Покерная рука
  • Игра в покер
  • Стопка сброса (возможно)
  • Игрок
  • Ставки

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

Колода карт

  • Карты — в колоде должно быть 52 разные игральные карты
  • Перетасовка — колода должна сама перетасовываться
  • Сдать карту — колода должна иметь возможность снимать карту с себя и сдавать ее игроку
  • Раздать руку — колода должна быть в состоянии раздать руку игроку или группе игроков

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

Инкапсуляция

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

В нашем классе колоды 52 карты. Класс игрока не должен иметь возможности выбрать любую карту из колоды или изменить порядок колоды вручную. Вместо этого игрок может получить только руку. Говорят, что содержимое колоды равно 9.0112 инкапсулирует в класс колоды, потому что колода владеет массивом карт и не позволяет другим классам получать к ней прямой доступ.

Абстракция

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

Продолжая наш пример, если бы у вас был класс колоды карт и вы увидели, что можете вызвать функцию . shuffle() или функцию .deal() , вы бы хорошо поняли, что такое класс. делает и какую функциональность он предоставляет без необходимости понимать, как эти функции работают внутри.

Наследование

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

Полиморфизм

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

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

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