Объектно ориентированное программирование (ООП)
Объе́ктно-ориенти́рованное программи́рование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
Состоит:
- Инкапсуляция - Сокрытие данных
-
Наследование - Возможность описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
-
Полиморфизм – Это когда подкласс класса может вызвать ту же самую обобщенную унаследованную функцию в своем собственном контексте.
ООП в функциональном стиле
Внутренний и внешний интерфейс
(Инкапсуляция)

Внутренний и внешний интерфейс
(Инкапсуляция)


- Внутренний интерфейс (private) – это свойства и методы, доступ к которым может быть осуществлен только из других методов объекта, их также называют «приватными» (есть и другие термины, встретим их далее).
- Внешний интерфейс (public) – это свойства и методы, доступные снаружи объекта, их называют «публичными».
Публичное и приватное свойство,
пример
function CoffeeMachine(power) {
this.waterAmount = 0; // количество воды в кофеварке
alert( 'Создана кофеварка мощностью: ' + power + ' ватт' );
}
// создать кофеварку
var coffeeMachine = new CoffeeMachine(100);
// залить воды
coffeeMachine.waterAmount = 200;- Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.
- Свойства, записанные в this, можно считать публичными.
Публичный и приватный методы, константы, доступ к объекту из внутреннего метода
function CoffeeMachine(power) {
this.waterAmount = 0;
var WATER_HEAT_CAPACITY = 4200;
function getBoilTime() {
return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power;
}
function onReady() {
alert( 'Кофе готово!' );
}
this.run = function() {
setTimeout(onReady, getBoilTime.call(this));
};
}
// создаю кофеварку, мощностью 100000W чтобы кипятила быстро
var coffeeMachine = new CoffeeMachine(100000);
coffeeMachine.waterAmount = 200;
coffeeMachine.run();Геттеры и сеттеры
Для управляемого доступа к состоянию объекта используют специальные функции, так называемые «геттеры» и «сеттеры».
function CoffeeMachine(power, capacity) { // capacity - ёмкость кофеварки
var waterAmount = 0;
var WATER_HEAT_CAPACITY = 4200;
function getTimeToBoil() {
return waterAmount * WATER_HEAT_CAPACITY * 80 / power;
}
// "умная" установка свойства
this.setWaterAmount = function(amount) {
if (amount < 0) {
throw new Error("Значение должно быть положительным");
}
if (amount > capacity) {
throw new Error("Нельзя залить воды больше, чем " + capacity);
}
waterAmount = amount;
};
this.getWaterAmount = function() {
return waterAmount;
};
function onReady() {
alert( 'Кофе готов!' );
}
this.run = function() {
setTimeout(onReady, getTimeToBoil());
};
}
var coffeeMachine = new CoffeeMachine(1000, 500);
coffeeMachine.setWaterAmount(450);
alert( coffeeMachine.getWaterAmount() ); // 450Функциональное наследование
Наследование – это создание новых «классов» на основе существующих.
function Machine() {
var enabled = false;
this.enable = function() {
enabled = true;
};
this.disable = function() {
enabled = false;
};
}Базовый класс «машина»
function CoffeeMachine(power) {
Machine.call(this); // отнаследовать
var waterAmount = 0;
this.setWaterAmount = function(amount) {
waterAmount = amount;
};
}
var coffeeMachine = new CoffeeMachine(10000);
coffeeMachine.enable();
coffeeMachine.setWaterAmount(100);
coffeeMachine.disable();Унаследуем от него кофеварку. При этом она получит эти методы автоматически
Перенос свойства в защищённые
function Machine(power) {
this._power = power; // (1)
this._enabled = false;
this.enable = function() {
this._enabled = true;
};
this.disable = function() {
this._enabled = false;
};
}
function CoffeeMachine(power) {
Machine.apply(this, arguments); // (2)
alert( this._enabled ); // false
alert( this._power ); // 10000
}
var coffeeMachine = new CoffeeMachine(10000);Переопределение методов
function Machine(power) {
this._enabled = false;
this.enable = function() {
// используем внешнюю переменную вместо this
this._enabled = true;
};
this.disable = function() {
this._enabled = false;
};
}
function CoffeeMachine(power) {
Machine.apply(this, arguments);
var waterAmount = 0;
this.setWaterAmount = function(amount) {
waterAmount = amount;
};
var parentEnable = this.enable;
this.enable = function() {
parentEnable.call(this);
this.run();
}
function onReady() {
alert( 'Кофе готово!' );
}
this.run = function() {
setTimeout(onReady, 1000);
};
}
var coffeeMachine = new CoffeeMachine(10000);
coffeeMachine.setWaterAmount(50);
coffeeMachine.enable();ООП в прототипном стиле
Прототип объекта
Свойство __proto__
доступно во всех браузерах, кроме IE10-, а в более старых IE к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
var animal = {
eats: true
};
var rabbit = {
jumps: true,
//eats: false
};
rabbit.__proto__ = animal;
// в rabbit можно найти оба свойства
alert( rabbit.jumps ); // true
alert( rabbit.eats ); // true
Объект, на который указывает ссылка __proto__, называется «прототипом». В данном случае получилось, что animal является прототипом для rabbit.
Метод hasOwnProperty
var animal = {
eats: true
};
var rabbit = {
jumps: true,
__proto__: animal
};
for (var key in rabbit) {
// выводит и "eats" и "jumps"
alert( key + " = " + rabbit[key] );
}var animal = {
eats: true
};
var rabbit = {
jumps: true,
__proto__: animal
};
for (var key in rabbit) {
// пропустить "не свои" свойства
if (!rabbit.hasOwnProperty(key)) continue;
// выводит только "jumps"
alert( key + " = " + rabbit[key] );
}Object.create(null)
var data = {};
data.text = "Привет";
data.age = 35;
// функция, хотя мы её туда не записывали
alert(data.toString); Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту:
var data = {};
// выведет toString только если оно записано в сам объект
alert(data.hasOwnProperty('toString') ? data.toString : undefined);var data = Object.create(null);
data.text = "Привет";
alert(data.text); // Привет
alert(data.toString); // undefinedОднако, есть путь и проще:
Прототип объекта
Свойство prototype
var animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
this.__proto__ = animal;
}
var rabbit = new Rabbit("Кроль");
alert( rabbit.eats ); // true, из прототипаСвойство F.prototype и создание объектов через new
var animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
// rabbit.__proto__ == animal
var rabbit = new Rabbit("Кроль");
- Установка Rabbit.prototype = animal буквально говорит интерпретатору следующее: "При создании объекта через new Rabbit запиши ему __proto__ = animal".
- Оператора new, оно вообще ничего не делает, его единственное назначение – указывать __proto__ для новых объектов.
function Rabbit() {}
Rabbit.prototype = {
constructor: Rabbit
};
alert( Rabbit.prototype.constructor == Rabbit ); // true
Свойство constructor
У каждой функции по умолчанию уже есть свойство prototype. Оно содержит объект такого вида:
function Rabbit(name) {
this.name = name;
alert( name );
}
var rabbit = new Rabbit("Кроль");
var rabbit2 = new rabbit.constructor("Крольчиха");Встроенные "классы" в JavaScript
Откуда методы у {} ?
var obj = {};
console.log( obj.toString() ); // "[object Object]" ?console.log( {}.__proto__.toString ); // function toStringОткуда новый объект obj получает такой __proto__?
- Запись obj = {} является краткой формой obj = new Object, где Object – встроенная функция-конструктор для объектов.
- При выполнении new Object, создаваемому объекту ставится __proto__ по prototype конструктора, который в данном случае равен встроенному Object.prototype.
- В дальнейшем при обращении к obj.toString() – функция будет взята из Object.prototype.

Проверка методов у {} ?
var obj = {};
// метод берётся из прототипа?
alert( obj.toString == Object.prototype.toString ); // true, да
// проверим, правда ли что __proto__ это Object.prototype?
alert( obj.__proto__ == Object.prototype ); // true
// А есть ли __proto__ у Object.prototype?
alert( obj.__proto__.__proto__ ); // null, нетВстроенные «классы» в JavaScript в схемах

Вызов методов через call и apply из прототипа
function showList() {
alert( [].join.call(arguments, " - ") );
}
showList("Вася", "Паша", "Маша"); // Вася - Паша - Машаfunction showList() {
alert( Array.prototype.join.call(arguments, " - ") );
}
showList("Вася", "Паша", "Маша"); // Вася - Паша - МашаИзменение встроенных прототипов
String.prototype.repeat = function(times) {
return new Array(times + 1).join(this);
};
alert( "ля".repeat(3) ); // ляляляObject.prototype.each = function(f) {
for (var prop in this) {
// пропускать свойства из прототипа
if (!this.hasOwnProperty(prop)) continue;
var value = this[prop];
f.call(value, prop, value);
}
};
// Теперь все будет в порядке
var obj = {
name: 'Вася',
age: 25
};
obj.each(function(prop, val) {
alert( prop ); // name -> age
});if (!Object.create) {
Object.create = function(proto) {
function F() {}
F.prototype = proto;
return new F;
};
}| Достоинства | Недостатки |
|---|---|
| Методы в прототипе автоматически доступны везде, их вызов прост и красив. |
Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен. Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения. |
ООП функциональный или прототипный стиль
ООП функциональный или прототипный стиль
function Animal(name) {
var speed = 0;
this.name = name;
this.run = function(val) {
speed += val;
alert( this.name + ' бежит, скорость ' + speed );
};
this.stop = function() {
speed = 0;
alert( this.name + ' стоит' );
};
};
var animal = new Animal('Зверь');
animal.run(3); // Зверь бежит, скорость 3
animal.run(10); // Зверь бежит, скорость 13
animal.stop(); // Зверь стоит// конструктор
function Animal(name) {
this.name = name;
this.speed = 0;
}
// методы в прототипе
Animal.prototype.run = function(speed) {
this.speed += speed;
alert( this.name + ' бежит, скорость ' + this.speed );
};
Animal.prototype.stop = function() {
this.speed = 0;
alert( this.name + ' стоит' );
};
var animal = new Animal('Зверь');
alert( animal.speed ); // 0, свойство взято из прототипа
animal.run(5); // Зверь бежит, скорость 5
animal.run(5); // Зверь бежит, скорость 10
animal.stop(); // Зверь стоит// --------- Класс-Родитель ------------
function Animal(name) {
this.name = name;
this.speed = 0;
}
Animal.prototype.jump = function() {
alert(this.name + " бежит!")
}
// --------- Класс-потомок -----------
// Конструктор потомка
function Rabbit(name) {
Animal.apply(this, arguments);
}
// Унаследовать
Rabbit.prototype = Object.create(Animal.prototype);
// Желательно и constructor сохранить
Rabbit.prototype.constructor = Rabbit;
// Методы потомка
Rabbit.prototype.jump = function() {
// Вызов метода родителя внутри своего
Animal.prototype.jump.apply(this);
alert( this.name + " подпрыгивает!" );
};
// Готово, можно создавать объекты
var rabbit = new Rabbit('Кроль');
rabbit.jump();Структура наследования

| Достоинства | Недостатки |
|---|---|
| Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный – только свойства. Поэтому прототипный стиль – быстрее и экономнее по памяти. | При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором. |
Проверка класса: "instanceof"
function Rabbit() {}
// создаём объект
var rabbit = new Rabbit();
// проверяем -- этот объект создан Rabbit?
alert( rabbit instanceof Rabbit ); // true, верноvar arr = [];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true// Создаём объект rabbit, как обычно
function Rabbit() {}
var rabbit = new Rabbit();
// изменили prototype...
Rabbit.prototype = {};
// ...instanceof перестал работать!
alert( rabbit instanceof Rabbit ); // falserabbit.__proto__ == Rabbit.prototype.
arr.__proto__.__proto__ == Object.prototype.Алгоритм проверки obj instanceof Constructor:
The End
RD Prototype
By Sarhan Azizov
RD Prototype
- 389