ООП в JavaScript
Первый рабочий пример по ООП в JavaScript. Рассмотрена функция-конструктор, добавление новых методов и свойств в неё через её прототип, наследование через new и Object.create, работа геттеров и сеттеров в конструкторе.
Ссылка на работающий пример: http://it.maksimslepov.ru/examples/oop1.html JS-код:
(function(){
var Counters = {
clickCounter : 0
}
// экземпляр-прототип
function Menu(options) {
var self = this,
element = options.elem,
button = options.button,
isOpen = false;
button.addEventListener('click', function(event){
event.preventDefault();
self.setOpen();
}, true);
// геттер текущей кнопки
this.getButton = function() {
return button;
}
// геттер текущего элемента
this.getElement = function() {
return element;
}
this.open = function() {
self.setElement().style.display = 'block';
self.setButton().classList.add('active');
isOpen = true;
}
this.close = function() {
self.setElement().style.display = 'none';
self.setButton().classList.remove('active');
isOpen = false;
}
// геттер состояния (открыто/закрыто)
this.isOpen = function(newOpen) {
if (newOpen) isOpen = newOpen;
return isOpen;
}
// сеттер состояния (меняет в любую сторону)
this.setOpen = function() {
if (isOpen) {
self.close();
} else {
self.open();
}
}
// сеттер кнопки, меняет, есои подать новое значение
this.setButton = function(newButton) {
if (newButton) button = newButton;
return button;
}
// сеттер элемента, меняет, если подать новое значение
this.setElement = function(newElement) {
if (newElement) element = newElement;
return element;
}
}
// создаём первый экземпляр
var menuItem = new Menu({
elem : document.getElementsByClassName('menu')[0],
button : document.getElementsByClassName('do-menu')[0]
})
// проверка работы сеттера
var x = menuItem.isOpen()
console.log(x);
menuItem.setOpen();
var y = menuItem.isOpen()
console.log(y);
// создаём новый метод в экземпляре
menuItem.getOpen = function() {
var self = this;
self.getButton().addEventListener('click', function(event) {
event.preventDefault();
var open = self.isOpen();
if (!open) alert('getOpen');
}, true);
}
// создаём дочерний экземпляр от первого
var menuItem2 = Object.create(menuItem);
// создаём новый метод у второго экземпляра
menuItem2.getOpen2 = function() {
var self = this;
self.getButton().addEventListener('click', function(event) {
event.preventDefault();
var open = self.isOpen();
if (!open) alert('getOpen2');
}, true);
}
// создаём новый метод у первого экземпляра
menuItem.getOpen3 = function() {
var self = this;
self.getButton().addEventListener('click', function(event) {
event.preventDefault();
var open = self.isOpen();
if (!open) alert('getOpen3');
}, true);
}
// создаём новый экземпляр
var secondItem = new Menu({
elem : document.getElementsByClassName('menu')[1],
button : document.getElementsByClassName('do-menu')[1]
})
/* ========= для выполнения проверок отдельные блоки нужно разкомментировать ========= */
/*// вызовем функцию с контекстом выполнения: будет ошибка, т.к. в глобальном объекте нет функции getButton()
menuItem2.getOpen.call(this);*/
/*// во втором экземпляре удалить кастомный метод первого экземпляра нельзя
delete menuItem2.getOpen;
// метод всё-равно останется
console.log(menuItem2);*/
/*// вызываем кастомный метод у дочернего экземпляра, затем удаляем его
menuItem2.getOpen2();
delete menuItem2.getOpen2;
// тут будет ошибка, т.к. метод удалили
menuItem2.getOpen2();*/
/*// если удаляем метод в родительском экземпляре, то в дочерних он перестаёт существовать автоматически
delete menuItem.getOpen;
// возникает ошибка
menuItem2.getOpen();
// однако при создании нового метода у родительского экземпляра он автоматически появляется в потомках
menuItem2.getOpen3();*/
/* ========= конец выполнения проверок ========= */
/* ========= для выполнения проверок отдельные блоки нужно разкомментировать ========= */
/*// нельзя добавить новый метод или свойство непосредственно в конструктор
Menu.method = function() {alert('method!')};
Menu.var = 'variable';
// в консоли не увидим ничего нового
console.log(secondItem); */
/*// однако можно добавить новые свойства и методы в прототип конструктора
Menu.prototype.method = function() {alert('method!')};
Menu.prototype.var = 'variable';
console.log(secondItem);
// кстати, новые свойства и методы появляются и в другим созданных этим конструктором дочерних элементах
console.log(menuItem, menuItem2); */
/* ========= конец выполнения проверок ========= */
/* == после 5 кликов по любому из заголовков меню меняем управляющие заголовки местами == */
// функция замены кнопок
function reverse() {
// меняем местами кнопки, учитывая текущее состояние (открыто/закрыто)
var newOpen1 = menuItem.isOpen(),
newButton1 = secondItem.getButton(),
newOpen2 = secondItem.isOpen(),
newButton2 = menuItem.getButton();
menuItem.setOpen(newOpen1);
menuItem.setButton(newButton1);
secondItem.setOpen(newOpen2);
secondItem.setButton(newButton2);
}
// в прототипе конструктора заводим новый метод, появляется сразу во всех дочерних элементах
Menu.prototype.doReverse = function() {
this.getButton().addEventListener('click', function(){
Counters.clickCounter++;
if (Counters.clickCounter > 4) reverse();
}, true)
}
// запускаем новый метод у обоих экземпляров меню
menuItem.doReverse();
secondItem.doReverse();
})();
Резюме: 1) В конструктор нельзя напрямую добавлять методы и свойства, можно через прототип конструктора 2) Изменения в конструкторе в любом месте кода влекут изменения в наследниках (добавление/удаление методов, свойств) 3) В дочернем элементе, созданном через Object.create, нельзя удалить метод, заданный в родительском элементе (delete newObj.method) 4) Ключевая запись var self = this в конструкторе, позволяет методам ссылаться на контекст конструктора 5) Options в конструктор (function Constr(options)) передаются в следующем виде: var newObj = new Constr({option1 : 1, option2 : 'some'});