Время прочтения: 7 мин.

В материале использовался jQuery 3.4.1 и браузеры ЯндексБраузер 22.7.5.940 (64-bit), Microsoft Edge 105.0.1343.27 (Официальная сборка) (64-разрядная версия).

Ранее я показывала пример с формой регистрации, в котором рассматривались: методы и переменные класса, сеттеры и геттеры, вызов объектов класса. В этой части я покажу на примере: как использовать наследование, инкапсуляцию и полиморфизм в JS. Если вы еще не знакомы с первой частью, то рекомендую сначала прочитать ее.

Прежде чем начать, необходимо создать файловую структуру и вспомогательные скрипты для дальнейшей демонстрации.

Изменю файловую структуру формы регистрации из предыдущей части следующим образом: Создам в корне проекта каталоги «static», «template». Перенесу в «static» ранее созданные папки css, js, lib; а в директорию «template» перенесу файл index.html.

В ООП программировании существуют основные принципы:

  1. Наследование
  2. Полиморфизм
  3. Инкапсуляция

Также существует принцип абстракции, но здесь он рассматриваться не будет.

Рассмотрим каждый принцип в JS и разберем на примерах по порядку.

Наследование – это когда один класс получает свойства и методы другого класса.

Создам файл workspace\form_es6\static\js\hello.js с классом hello и методами, которые будут возвращать приветствие в зависимости от времени суток:

hello.js

class hello {

	constructor(v_curdatetime){
		
		//текущее дата/время
		this.curdatetime = v_curdatetime;

		//массив приветствия 
		this.arr_goodday = new Array("Доброе утро", "Добрый день", "Добрый вечер", "Доброй ночи");
		
	}

	setHour(datetime){
		const hh = datetime.getHours();
		return hh;
	}
			
	salute(v_fio){
		const hh = this.setHour(this.curdatetime);
		let out = '';
		let str_goodday = '';
		if(hh >= 6 && hh < 12){
			str_goodday = this.arr_goodday[0];
			out = str_goodday +" "+ v_fio;			
		}
		
		if(hh >= 12 && hh < 18){
			str_goodday = this.arr_goodday[1];
			out = str_goodday +" "+ v_fio;
			
		}
		
		if(hh >= 18 && hh < 0){
			str_goodday = this.arr_goodday[2];
			out = str_goodday +" "+ v_fio;
		}
		
		if(hh >= 0 && hh < 6){
			str_goodday = this.arr_goodday[3];
			out = str_goodday +" "+ v_fio;
		}
		console.log(out);
		return out;
	}
}

Добавлю в файл workspace\form_es6\template\index.html линк на новый файл hello.js:

<script type="text/javascript" src="/static/js/hello.js"></script>
<script type="text/javascript" src="/static/js/form1ES6.js"></script>
</head>

В файле workspace\form_es6\static\js\form1ES6.js и изменю строки, как представлено ниже:

form1ES6.js

class form1 extends hello{

	constructor(curdatetime){
		super(curdatetime);

                …

        }
}

Теперь класс form1 наследует свойства и методы класса hello. В данном случае класс form1 является дочерним классом, а класс hello – это суперкласс или родительский класс. Ключевое слово extends означает связь между двумя классами. Ключевое слово super означает, что в конструкторе дочернего класса form1 вызывается конструктор родительского класса hello. В конструктор родительского класса передаются значения переменных дочернего класса.

При вызове дочернего класса выполнятся не только методы класса form1, но и класса hello.

Можно вызвать метод salute родительского класса «hello» при загрузке страницы в событии «Submit»:

$(document).ready(function() {
	let curdatetime = new Date();
	const objform1 = new form1(curdatetime); //Передаем значение текущего времени 
	
        //можно вызвать метод salute родительского класса «hello» 
       //при загрузке страницы в событии «Submit»
       $("#registrity").submit(function(event){
	     objform1.salute($("#fio").val());
	});
})

или вызвать этот метод в дочернем классе:

form1ES6.js
class form1 extends hello{
…
init(){
		…
		
		this.form1.on('submit',(e)=>{
	
			const fio = this.fio.val();
			…
			const goodday_str = this.salute(fio);			
			…
    	
    			return false;

		});
	}

Заполнив форму в браузере и нажав кнопку «Отправить», можно увидеть результаты в панели консоли, которые возвращает метод salute(fio) класса hello:

Полиморфизм – дословно означает: «много форм». Это возможность объектов и методов с одинаковыми наименованиями принимать разные формы.

Полиморфизм бывает двух основных типов:

  1. Переопределение методов
  2. Перегрузка функций

Переопределение методов – это когда при наследовании классов функцию родительского класса можно переопределить в дочернем классе. Покажу на примере.

В файле workspace/form_es6/static/js/form1ES6.js добавлю в класс «form1» метод «salute»:

form1ES6.js

salute(v_fio){
		const hh = this.setHour(this.curdatetime);
		let out = '';
		let str_goodday = '';
		if(hh >= 6 && hh < 12){
			str_goodday = 'Good morning';
			out = str_goodday +" "+ v_fio;			
		}
		
		if(hh >= 12 && hh < 18){
			str_goodday = 'Good afternoon';
			out = str_goodday +" "+v_fio
			
		}
		
		if(hh >= 18 && hh < 0){
			str_goodday = 'Good evening';
			out = str_goodday +" "+v_fio
		}
		
		if(hh >= 0 && hh < 6){
			str_goodday = 'Good night';
			out = str_goodday +" "+v_fio
		}
		console.log(out);
		return out;
	}

Затем вызову метод «salute» при загрузке страницы:

$(document).ready(function() {
	
	let curdatetime = new Date();
	
	const objform1 = new form1(curdatetime); //Передаем значение текущего времени 
	
	$("#registrity").submit(function(event){
		objform1.salute($("#fio").val()); //вызов метода «salute» с параметром fio
	});		
})

Теперь в нашем проекте два метода «salute»: один метод в родительском классе hello, другой – в дочернем классе form1. Но при вызове метода «salute» в сабмите формы выведется результат дочернего метода:

Таким образом, можно не только переопределить метод, но и дополнить (расширить) его дополнительными свойствами. Например, если в методе «salute» в классе form1 добавить вызов метода родительского класса «hello» — super.salute(v_fio)

salute(v_fio){
		const hh = this.setHour(this.curdatetime);
		let out = '';
		let str_goodday = '';
		
		super.salute(v_fio);
		
		if(hh >= 6 && hh < 12){
			str_goodday = 'Good morning';
			out = str_goodday +" "+ v_fio;			
		}
		
		…
		console.log(out);
		
		return out;
	}

, то получится результат двух методов разных классов:

Перезагрузка методов – это создание нескольких функций с одинаковыми наименованиями, но разным количеством параметров или разными типами.

Т.е., образно говоря:

Func1(val1='', val2=''){
	
	return val1+' '+ val2;
}
		
Func1(val1=0){
	
	return val1;
}

Если выполнить функцию: Func1(‘go’, ‘walk’), то вернется результат: ‘go walk’.

А если выполнить функцию Func1(10), то вернется результат: 10.

В большинстве языков с поддержкой ООП такой полиморфизм работает, но не в JS. Если создать две функции с одинаковыми наименованиями, но разными входными параметрами, то JS все равно переопределит функцию. Но в JS есть схожий механизм с перезагрузкой методов при использовании сеттеров и геттеров. В первой части статьи я приводила пример и показывала, как работают вызовы:

//описание в классе
get HelloFIO(){
	console.log('getter: '+this.hfio);
	return this.hfio;
}
		
set HelloFIO(helloWorld){
	console.log('setter: '+helloWorld);
	this.hfio = helloWorld;	
}

При вызове данного метода будет возвращен разный результат в зависимости от входных параметров этих методов:

$(document).ready(function() {
	const objform1 = new form1('Доброе утро');  
        objform1.HelloFIO; //Вызываем без параметра, сработает getter
	objform1.HelloFIO= 'Добрый день'; //Вызываем с параметром = 'Добрый день', сработает setter
})

Инкапсуляция – защита связанных данных объекта от влияния извне.

До некоторых пор приватные методы и приватные переменные JS не поддерживало. И ранее, по общей договоренности разработчиков, нижнее подчеркивание перед наименованием методов и переменных означало приватность:

_pass;
this._pass;
_func1();

Для самого же JS это не имело значения в плане видимости. Но, на данный момент, самые последние браузеры стали действительно поддерживать настоящую приватность методов и переменных класса. Чтобы сделать метод или переменную класса приватным, необходимо перед наименованием поставить знак «#». Пример, я создала приватную переменную #pass:

class form1 extends hello{

       #pass;

       constructor(curdatetime, v_fio, v_pass){
	    super(curdatetime);
	    …
		
	    //приватная переменная
	    this.#pass = v_pass;
	    this.init();
	}

Теперь, при обращении к ней


$(document).ready(function() {
	
	const objform1 = new form1(curdatetime,); 
	
	$("#registrity").submit(function(event){
		
              objform1.#pass; //обращение к приватной переменной
	
        });

})

браузер, который поддерживает данный синтаксис, вернет ошибку:

Uncaught SyntaxError: Private field '#pass' must be declared in an enclosing class

Таким образом, нельзя напрямую обратиться к приватной переменной извне.

Но можно вернуть ее в методе класса:

get_pass () {
	return this.#pass;
}

При вызове этого метода извне, переменная вернет значение:

$(document).ready(function() {
	
	const objform1 = new form1(curdatetime, 'parol1111'); //Передаем значение текущего времени и  строчку с паролем 
	
	$("#registrity").submit(function(event){
		//objform1.#pass;
		objform1.get_pass();
	});

К сожалению, данный синтаксис не является кросс-браузерным, поэтому на данном этапе не рекомендую пока его использовать. Стоит подождать пару лет, пока все браузеры начнут поддерживать приватность.

В этой части я постаралась разобрать основные принципы ООП и показала на примерах возможности JS ES>=6. Конечно же, во второй части рассмотрены не все возможности и нюансы. Но, думаю, для перехода от процедурного кода в ООП – вполне достаточно. В 3-ей части я продемонстрирую: как передавать данные от клиента на сервер с помощью микроweb-сервера, созданного с помощью python.