ООП в 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'});

Теги: