Когда речь идет об объектно-ориентированном программировании - подразумевается моделирование реальных объектов и отношений.
В ООП объект представляет собой блок, содержащий информацию (состояние / атрибуты) и операции (методы).
Ключевое слово this
this
- это объект, свойством которого является функция;this
- дает функциям доступ к своему объекту и его свойствам;this
- помогает выполнить один и тот же код для нескольких объектов;this
- можно рассматривать как кто меня вызвал?; т.е. то, что находится слева от точки. Например,window.a()
;this
- имеет динамическую область, т. е. не важно, где он был написан, важно, где он был вызван.
const obj = {
name: 'Alex',
sing() {
console.log('a this ', this);
var anotherFunc = function() {
console.log('b this ', this);
}
anotherFunc();
}
};
obj.sing();
// a this {name: "Alex", sing: ƒ}
// b this Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
var b = {
name: 'jay',
say() {
console.log('this is ', this);
}
}
b.say()
// this is {name: "jay", say: ƒ}
var c = {
name: 'jay',
say() {
return function () {
console.log('this is ', this);
}
}
}
c.say()()
// this is Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
var d = {
name: 'jay',
say() {
// стрелочная функция не имеют собственного контекста выполнения.
return () => console.log('this is ', this);
}
}
d.say()()
// this is {name: "jay", say: ƒ}
Стрелочные функции связывают this
с лексической областью действия.
const obj = {
name: 'Alex',
sing() {
console.log('a this ', this);
var anotherFunc = () => {
console.log('b this ', this);
}
anotherFunc();
}
};
obj.sing();
// a this {name: "Alex", sing: ƒ}
// b this {name: "Alex", sing: ƒ}
Прототип
- Прототип (prototype) - это экземпляр рабочего объекта. Объекты наследуются напрямую от других объектов.
__proto__
является ссылкой на свойство прототипа родительского объекта, например:
const obj = {};
obj.__proto__ === Object.prototype // true
- Свойство prototype принадлежит только функциям, в частности, функциям конструктора. Конструктор Object создает обертку объекта.
- Свойства proto и prototype используются для создания цепочки наследования свойств между объектами, начиная с Object и Primitive Types.
Object.create()
можно использовать для создания объектов с его свойством proto, связанным со свойством prototype объекта, переданного в качестве аргументаObject.create()
.- Object - это базовая функция (конструктор). Корнем всего в JavaScript является Object, который на самом деле является функцией.
typeof Object // "function"
Object имеет свойство prototype, которое является базовым объектом для всех вещей в JavaScript, включая функции JavaScript.
typeof Object.prototype // "object"
ES6 Классы
- Ключевое слово class в JS - синтаксический сахар. Под капотом он всё еще использует прототипное наследование (prototypal inheritance).
- Экземпляры класса должны создаваться с ключевым словом new.
- Метод constructor используется для создания экземпляра state (данных) нового объекта. State обычно уникально для каждого экземпляра.
- Функции обычно не включаются в конструктор, так как они создают ссылку на место в памяти в каждом новом экземпляре класса. Таким образом используя больше памяти, чем необходимо. Включая функции в качестве методов класса, экземпляры класса могут ссылаться на функцию через цепочку прототипов.
- Прототипное наследование (prototypal inheritance) имеет лучшую эффективность памяти, чем классическое наследование, благодаря тому, что оно разделяет ссылки памяти своих свойств прототипа с теми объектами, которые наследуют от него. В классическом наследовании, экземпляры класса создают новые ссылки на память для каждого унаследованного свойства.
Object.create() vs. Classes
- Оба
Object.create()
иclass
являются способами создания цепочки прототипов. - Некоторые люди предпочитают избегать ключевые слова constructor, class и this, чтобы ограничить путаницу из-за this.
- Другие предпочитают использовать ключевые слова constructor, class и this, возможно, из-за его сходства с другими языками с парадигмой объектно-ориентированного программирования.
Private vs. Public vs. Protected
Во многих объектно-ориентированных языках программирования, которые имеют классы, идея private и public полей действительно важна. В JavaScript этого нет. Ранее, если нужно было сделать поле private, к которому нельзя обращаться из класса, мы добавляли подчеркивание _
перед именем, чтобы другие программисты знали, что это private метод. Но, к сожалению, подчеркивание на самом деле ничего не делает.
В JavaScript есть предложение ECMAScript, которое предназначено для объявлений полей класса.
Это модификаторы доступа, которые помогают нам реализовать Encapsulation (или скрытие информации). Они сообщают компилятору, какие другие классы должны иметь доступ к определенному полю или методу.
Private - только текущий класс будет иметь доступ к полю или методу. Protected - только текущий класс и подклассы этого класса будут иметь доступ к полю или методу. Public - любой класс может ссылаться на поле или вызывать метод.
Так как в Javascript таких полей пока нет, для их реализации мы можем использовать TypeScript.
4 принципа ООП
Инкапсуляция
Инкапсуляция включает в себя идею о том, что данные объекта не должны быть напрямую доступны. Нужно вызывать методы вместо прямого доступа к данным. Инкапсуляция позволяет нам скрывать/показывать свойства функций.
ООП заключаем код в блоки, которые связаны друг с другом, чтобы эти блоки могли просто взаимодействовать друг с другом, используя методы и свойства, которые мы делаем доступными. Данный принцып делает код проще в обслуживании и более пригодным для повторного использования.
Инкапсуляция с использованием замыкания
const createCounter = () => {
// Переменная, определенная в области действия фабрики или конструктора
// является приватной для этой функции.
let count = 0;
return ({
// Любые другие функции, определенные в той же области, являются привилегированными:
// Они имеют доступ к закрытой переменной `count`
// определенной в любом месте их цепочки областей видимости (содержащей области действия функции).
click: () => count += 1,
getCount: () => count.toLocaleString()
});
};
const counter = createCounter();
counter.click();
counter.click();
counter.click();
console.log(counter.getCount()); // "3"
Абстракция
Абстракция - это способ создания простой модели, которая содержит только важные свойства с точки зрения контекста приложения, из более сложной модели. Иными словами - это способ скрыть детали реализации и показать пользователям только функциональность. Абстракция игнорирует нерелевантные детали и показывает только необходимые. Важно помнить, что мы не можем создать экземпляр абстрактного класса.
Всё программное обеспечение - это абстракция, скрывающая всю тяжелую работу и бездумные детали.
Многие программные процессы повторяются снова и снова. Поэтому, на этапе декомпозиции проблемы, мы удалим дублирование, записывая какой-либо компонент (функцию, модуль, класс и т. Д.), присваивая ему имя (идентификатор) и повторно используя его столько раз, сколько нам нужно.
Процесс декомпозиции - это процесс абстракции. Успешная абстракция подразумевает, что результатом является набор независимо полезных и перекомпонованных компонентов.
Полиморфизм
Полиморфизмом является одним из принципов объектно-ориентированного программирования (ООП). Это помогает проектировать объекты таким образом, чтобы они могли совместно использовать или переопределять любое поведение с конкретными предоставленными объектами.
Само слово означает много форм. Существует много толкований того, что именно оно означает, но идея заключается в способности вызывать один и тот же метод для разных объектов, и при этом каждый объект реагирует по-своему.
Чтобы это произошло полиморфизм использует наследование.
В следующем примере дочерний объект, такой как Coder
, переопределяет метод say
, вызванный из родительского объекта Human
, и возвращает новую строку соответственно. Тогда как другой дочерний объект Men
, вместо переопределения метода say
, наследует его и отображал родительскую строку.
class Human {
constructor(name) {
this.name = name;
}
say() {
return `Hello, my name is ${this.name}, I like travelling`;
}
}
class Men extends Human {
constructor(name) {
super(name)
}
// Берем метод say у родителя.
}
class Coder extends Human {
constructor(name) {
super(name)
}
say() {
// Переопределяем метод родителя say для отображения нового значения.
return `Hello, my name is ${this.name}, I like coding`;
}
}
const alex = new Men('Alex');
const leo = new Coder('Leo');
alex.say() // "Hello, my name is Alex, I like travelling"
leo.say() // "Hello, my name is Leo, I like coding"
Наследование
Наследование - это механизм базирования объекта или class на другом объекте (наследование на основе прототипа) или class (наследование на основе класса). Мы избегаем необходимости переписывать один и тот же код, а также экономим пространство памяти, используя общие методы.
class Human {
constructor(name) {
this.name = name;
}
sayMyName() {
return 'Hello, I am ' + this.name;
}
}
class Men extends Human {
constructor(name) {
super(name)
}
}
class Coder extends Human {
constructor(name) {
super(name)
}
}
const alex = new Men('Alex');
const leo = new Coder('Leo');
alex.sayMyName() // Hello, I am Alex
leo.sayMyName() // Hello, I am Leo
Prototypal Inheritance
Программирование на основе прототипов - это стиль объектно-ориентированного программирования, в котором повторное использование поведения (известное как наследование) выполняется через процесс повторного использования существующих объектов посредством делегирования, которые служат как prototypes. Сторонники программирования на основе прототипов утверждают, что данный стиль поощряет программиста сосредоточиться на поведении некоторого набора примеров и лишь позднее, беспокоиться о классификации этих объектов в архетипические объекты, которые впоследствии используются аналогично классам.
Classical Inheritance
Программирование на основе классов, или же, ориентация на классы, - это стиль объектно-ориентированного программирования (ООП), в котором наследование происходит через определение классов объектов, вместо наследования, которое происходит только через объекты.
Tight Coupling (сильная связанность) относится к волновым эффектам, которые могут произойти с подклассами (дочерние классы), когда вносится изменение в суперкласс (родительский класс).
- Tight Coupling может привести ко многим непреднамеренным эффектам для подклассов.
- Tight Coupling может помочь избежать повторений в коде.
- Хрупкая проблема базового класса является фундаментальной архитектурной проблемой систем объектно-ориентированного программирования, в которых базовые классы (суперклассы) считаются «хрупкими», потому что, казалось бы, безопасные модификации базового класса, когда они наследуются производным классом, могут привести к сбоям в работе производных классов.
- Проблема гориллы с бананом относится к проблеме наследования слишком много от суперкласса. «Будто тебе нужен банан, а ты получаешь банан в придачу с гориллой в джунглях».
- Классическое наследование требует превосходного предвидения, чтобы избежать проблем неправильного наследования.