Object Oriented Programming and OOP in Javascript
Concepts
- Abstraction
- Encapsulation
- Inheritance
- Polymorphism
Abstraction
Абстракция позволяет нам сосредоточиться на том, что объект делает, а не на том, как он это делает.
В Typescript реализованы абстрактные классы ключевым словом abstract
.
От абстрактного класса нельзя создать экземпляр. Пример с Revuud абстрактного класса - User, в том время как класс - Talent, Admin.
Цель - не дублируем код
link to abstract class example
Encapsulation
Это разделение интерфейсов на внутренний и внешний - приватные и публичные свойства/методы.
Цель - ограничить внешних пользователей от изменений внутреннего интерфейса, чтобы ничего не сломали внутри извне.
Есть конвенция называть приватные свойства начиная с _
На уровне языка реализуется символом #
:
class Person { #_privateField: 'value' #_privateMethod () {} } new Person()._privateField// undefined
Inheritance
Наследование классов – это способ расширения одного класса другим классом.
Цель - не дублировать код и экономить память, расширять уже существующий функционал.
Polymorphism
Это способность вызывать один и тот же метод для разных объектов, и при этом каждый объект реагирует по-своему. Чтобы это произошло полиморфизм использует наследование.
Example demonstrating all 4 concepts
abstract class User { abstract firstName: string; abstract lastName: string; abstract sendInvite(): void; //! 1. abstraction principle (no concrete implementation, only declaration) get fullName () { // non-abstract method with logic return this.firstName + this.lastName; // that's difference with interface, that you can write logic here }; } new User(); // Error, can't create instance from abstract class class Admin extends User { //! 3. inheritance principle (extending concrecte class from abstract) firstName; lastName; constructor(firstName: string, lastName: string) { super() this.firstName = firstName; this.lastName = lastName; } get fullName (){ //! 4. polymorphysm principle (overwrite parent fullName getter, so it works differently now) return this.firstName + ' last name is NDA for admins' } sendInvite() { //! 4. polymorphysm principle (implementing abstract method differently in 2 children classes) const author = this.fullName alert({type: 'admin', author}) //fake send function } } class Talent extends User { //! 3. inheritance principle (extending concrecte class from abstract) firstName; lastName; #age: number; //2. encapsulation (private field) constructor(firstName: string, lastName: string, age: number) { super() this.firstName = firstName; this.lastName = lastName; this.#age = age; } sendInvite() { //! 4. polymorphysm principle (implementing abstract method differently in 2 children classes const author = super.fullName //! 3. inheritance, call the parent-class getter alert({type: 'talent', author}) //fake send function } } const john = new Talent('John', 'Smith', 30); //! 2. encapsulation (private field is not available from outside of the Talent class) john.#age // undefined
OOP in Javascript
Common:
Function-constructor - new Promise()
это отдельный вид функции
- имя должно начинаться с большой буквы,
- должна вызываться при помощи оператора "new".
- такой вызов создаёт пустой this
Function-constructor (class) - Определяет характеристики объекта. Класс это шаблон по которому в дальнейшем создаются объекты (экземпляры).
Объект - Экземпляр класса создается с помощью функции оператора new - new constructor (arguments)
Class and instance creation:
function Car(model, year) { // class // constructor this.model = model; this.year = year; } // same in ES6+ class Car { constructor(model, year) { this.model = model; this.year = year; } } // call function-constructor (class) let laguna = new Car("laguna", 2002) // {model: "laguna", year: 2002}
Prototype inheritance
Prototype
prototype - это объект, содержащий поля,
которые будут переданы экземпляру-наследнику Array.prototype.map === [].map
и экземплярам дочерних классов Object.prototype.hasOwnProperty === [].hasOwnProperty
- !!! есть только у классов и function
- !!! нет у стрелочных функций
__proto__
link
- !!! это свойство-ссылка на prototype родительского класса/функции-конструктора
- !!!есть у любых типов данных кроме
null
&undefined
let array = []; // under the hood - new Array() array.__proto__ === Array.prototype
- boxing transforms primitives to objects
let age = 30; // under the hood - new Number(30) age.__proto__ === Number.prototype // boxing transforms age from 30 to // { [[Prototype]]: Number, [[PrimitiveValue]]: 30 }
- Движок, если не смог найти искомое свойство у объекта,
он через ссылку
__proto__
последует искать его в prototype родителя, от которого был создан и далее в родителя родителя, пока не достигнет объекта-родителя Object, который имеет прототип равныйnull
Child.hasOwnProperty() Child.prototype.__proto__ === Parent.prototype Parent.prototype.__proto__ === Object.prototype // .hasOwnProperty sits here, in protortype of Object Object.__proto__ === null
- ! Не нужно путать ссылку
__proto__
у прототипа и самого класса (функции)
Child.bind() Child.__proto__ === Parent.prototype Parent.__proto__ === Function.prototype // .bind sits here, in prototype of Function
super
keyword вызывающее конструктор суперкласса (родительского) или его методы
super([arguments]); // вызов родительского конструктора. super.functionOnParent([arguments]);
Getter & Setter:
У объекта помимо свойств и методов, есть свойства-аксеcсоры - get
и set
.
По своей сути это функции, которые используются для присвоения и получения значения, но во внешнем коде они выглядят как обычные свойства объекта.
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, //getter example set fullName(value) { [this.name, this.surname] = value.split(" "); }, //setter example }; // getter usage alert(user.fullName); // John Smith // setter usage user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper
Context:
THIS - слово this ссылается на текущий контекст,
это ссылка на объект в котором выполняется функция
позволяет вам работать со свойствами/методами класса
global scope:
no use strict - this === window
(Web) or global
(Node.js)
use strict - this == undefined
;
- Контекст(this) для стрелочных функци определяется в момент их создания.
const helloArrow = () => console.log('hello', this) // создана в глоб. области Car.sayHello = helloArrow; Car.sayHello() // выведет класс Window, т.к. была создана в глобальной области и **замкнулась** на класс Window
- Контекст(this) для
function(){}
определяется в момент их вызова и равен объекту перед точкой.\function helloFunc () { console.log('hello', this )} Car.sayHello = helloFunc; // (статический метод, присвоен классу) Car.sayHello() // выведет конструктор класса Car (класс перед точкой) laguna.sayHello = hello
Переопределение контекста, а также методы класса Function:
bind - let новаяФункция = стараяФункция.bind(новыйКонтекст, остальные Аргументы нужные старойФункции) передает новый контекст(но не вызывает функцию!)
call - тоже самое, только сразу же вызывает функцию
apply - тоже самое, но остальные аргументы в массиве -- стараяФункция.bind(новыйКонтекст, [остальные Аргументы, нужные старойФункции])
!!!Нельзя перепресвоить контекст для новойФункции повторно! повторно вызвав один из методов
Chaining
Нужно просто возвращать this
в каждом методе и сохранять в другое поле результат
Можно реализовать и в функции, и в объекте, и в классе
Удобно когда нужно сделать множество последовательных операций (с одной сущностью).
const main = { data: { i: 0, }, initiate: function (num = 0) { this.data.i = num; return this; }, add: function (num) { this.data.i += num; return this; }, subtract: function (num) { this.data.i -= num; return this; }, multiple: function (num) { this.data.i *= num; return this; }, divide: function (num) { this.data.i /= num; return this; }, print: function () { return this.data.i; }, }; const value = main.initiate(10).add(6).subtract(4).divide(3).multiple(2).print(); //8