Время прочтения: 13 мин.
Привет! Предлагаю рассмотреть пример объектно-ориентированного программирования (ООП) на JavaScript с использованием jQuery. На практике покажу, как инициализировать объекты, создавать методы и структуру класса. Данный материал поможет сделать рефакторинг вашего старого кода и перейти с процедурного кода JS на JS ООП, а также решить некоторые проблемы использования jQuery в классах.
В материале использовался jQuery 3.4.1 и браузеры ЯндексБраузер 19.3.1.828, Microsoft Edge 98.0.1108.43
За последнее время JavaScript, как клиентский язык, хорошо развился. Теперь JavaScript стал поддерживать стиль объектно-ориентированного программирования. Но, несмотря на это, до сих пор замечаю, что большинство web-приложений используют процедурный код на JS. Я думаю, что для этого есть несколько причин, которые тормозят процесс апгрейда клиентского кода фронтенда:
- Неравномерный переход на более свежие версии браузеров «пользователей».
- Неравномерная поддержка новой версии JS самими браузерами.
- Старые привычки — например, использование jQuery и некоторые сложности в правильном использовании данной библиотеки внутри классов.
Эти причины мешают создавать кроссбраузерные web-приложения. Поэтому программистам легче использовать проверенные и работающие скрипты, нежели использовать что-то новое и тратить время на тестирование.
Я не буду рассказывать про стандарты ECMAScript (ES) JavaScript и в чем разница между ES5, ES6, ES8 (и т.д.) в ООП JS – на эту тему материала достаточно много. Рассмотрю «живой» пример ООП на JavaScript с использованием jQuery. Расскажу, как инициализировать объекты и создавать методы класса. Создам структуру класса, на примере формы регистрации. Описанные приёмы помогут сделать первый шаг к рефакторингу вашего старого кода и перейти с процедурного кода JS на JS ООП, а также решить некоторые проблемы использования jQuery в классах.
Прежде чем приступить к разработке JS скрипта, проведите предварительную работу:
Создайте каталог c:/workspace/form_es6. В каталоге «form_es6» создайте форму регистрации:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Форма регистрации ES6</title>
<link rel="stylesheet" href="css/form_new.css">
<script type="text/javascript" src="lib/jquery/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="js/form1ES6.js"></script>
</head>
<body>
<form class="registrity" action="" method="post" id="registrity">
<fieldset class="f_form1">
<table id="tab_form">
<tr><th colspan="2"> Форма регистрации ES6 </th></tr>
<tr><td class="td_left top_pos">ФИО*:</td><td class="td_right top_pos"><input id="fio" type="text" placeholder="ФИО" required="required"></td></tr>
<tr><td class="td_left">Регион*:</td><td class="td_right">
<select id="region" type="select" class="set_change" required="required">
<option value="">-</option>
<option value="moscow_mo">Москва и МО</option>
<option value="other_reg">Другие регионы</option>
</select>
<span id="cur_time"></span>
</td></tr>
<tr style="display:none;" id="np_tr"><td class="td_left">н/п*:</td><td class="td_right">
<select id="np" type="select" class="set_change">
<option value="">-</option>
<option value="Архангельск" data-tmz="0" >Архангельск</option>
<option value="Астрахань" data-tmz="1" >Астрахань</option>
<option value="Белгород" data-tmz="-1" >Белгород</option>
<option value="Брянск" data-tmz="0" >Брянск</option>
<option value="Великий Новгород" data-tmz="0" >Великий Новгород</option>
<option value="Владивосток" data-tmz="7" >Владивосток</option>
</select>
</td></tr>
<tr><td class="td_left">email*:</td><td class="td_right"><input id="email" type="email" placeholder="Эл. адрес" required="required"></td></tr>
<tr><td class="td_left">Пароль*:</td><td class="td_right"><input id="pass" type="password" placeholder="*****" required="required"></td></tr>
<tr><td class="td_left">Дата рождения:</td><td class="td_right"><input id="birthdate" type="date"></td></tr>
<tr>
<td class="td_left">Пол:</td>
<td class="td_right">
<input id="s_w" type="radio" name="sex[]" value="ж"><span class="t_sex">ж</span>
<input id="s_m" type="radio" name="sex[]" value="м"><span class="t_sex">м</span>
</td>
</tr>
<tr><td class="td_left">О себе:</td><td class="td_right">
<textarea id="about" placeholder="Введите текст"></textarea></td></tr>
<tr>
<td colspan="2" class="td_left">
<button type="reset" id="bt_cleen">Очистить</button>
<button type="submit" id="bt_send">Отправить</button>
</td>
</tr>
</table>
</fieldset>
</form>
<div id="data_submit">
<p id="p_fio" class="result_submit"></p>
<p id="p_address" class="result_submit"></p>
<p id="p_email" class="result_submit"></p>
<p id="p_birthdate" class="result_submit"></p>
<p id="p_sex" class="result_submit"></p>
<p id="p_about" class="result_submit"></p>
</div>
</body>
</html>
Для используемого мной примера необходимо скачать библиотеку jQuery с официального сайта и поместить его в каталог c:/workspace/form_es6/lib/jquery. Главное – не забыть в index.html включить библиотеку jQuery:
<script type="text/javascript" src="lib/jquery/jquery-3.4.1.min.js"></script>
Создайте файл со стилями в каталоге c:/workspace/form_es6/css:
css/form_new.css
/*
* Оформление формы
*/
#tab_form,.registrity {
border:0;
width:420px;
margin:0 auto;
}
#tab_form .td_left{
color: #333399;
padding-left:0.5em;
padding-top:0.3em;
padding-bottom:0.3em;
padding-right:0.5em;
text-align: right;
vertical-align: center;
}
#tab_form .td_right{
padding-left:0.5em;
padding-top:0.3em;
padding-bottom:0.3em;
padding-right:0.1em;
text-align: left;
}
#tab_form th {
height:50px;
color: #333399;
vertical-align:middle;
text-align: center;
font-size: 16px;
border-bottom: 1px solid #9999CC;
}
#bt_send, #bt_cleen{
margin-right: 5px;
margin-top: 10px;
padding-left:10px;
padding-right:10px;
color:#333399;
border: 1px solid #666699;
height: 26px;
border-radius: 4px;
}
.f_form1{
border: 1px solid #666699;
background-color: #CCCCFF;
}
#tab_form .top_pos{
padding-top: 20px;
}
#about {
width:250px;
min-height: 72px;
line-height: 72px;
height:72px;
padding: 0 10px;
}
#birthdate {
color: #666666;
min-height: 20px;
line-height: 25px;
height:25px;
padding: 0 10px;
}
#tab_form .t_sex {
color: #666666;
margin-bottom: 8px;
margin-left: 3px;
}
#tab_form input, textarea {
border: 1px solid #666699;
}
#tab_form input[type="text"],
input[type="email"],
input[type="password"]
{
width:250px;
min-height: 20px;
line-height: 25px;
height:25px;
padding: 0 10px;
}
#tab_form
input[type="password"]::-webkit-input-placeholder
{
transform:translate3d(0,3px,0);
font-size: 15px;
}
#s_w, #s_m {
border: 1px solid #666699;
}
#data_submit {
width: 400px;
margin:0 auto;
border: 1px solid #666699;
margin-top:40px;
padding-left:5px;
}
.result_submit{
color: #333399;
font-weight: bold;
}
Страничка с простейшей формой подготовлена, теперь форма должна выглядеть вот так:

Далее, необходимо создать form1ES6.js файл и поместить его в каталог c:/workspace/form_es6/js.
Откройте form1ES6.js в редакторе, чтобы начать основную работу. Создайте класс:
js/form1ES6.js
class form1 {
}
Вызов класса можно прописать в том же файле form1ES6.js после закрывающей фигурной скобки класса:
class form1 {
//переменные и метода класса
…
}
//создать объект класса при загрузке страницы.
//вы можете поместить этот кусок в отдельный файл
$(document).ready(function() {
const objform1 = new form1();
})
А можно прописать вызов класса в отдельном файле (например: в callDocReady.js) и включить его на странице index.html
(<script type="text/javascript" src="js/callDocReady.js"></script>)
Внутри класса «form1» создайте конструктор — специальный метод, который вызывается автоматически при вызове класса. В конструкторе будут инициализироваться переменные класса и метод:
class form1 {
constructor(){
//Форма регистрации
this.form1 = $("#registrity");
//ФИО
this.fio = $("#fio");
//Регион
this.region = $("#region");
//Населенный пункт
this.np = $("#np");
//Эл адрес
this.email = $("#email");
//Пароль
this.pass = $("#pass");
//День рождения
this.birthdate = $("#birthdate");
//Пол
this.t_sex = $("input[name='sex[]']");
//О себе
this.about = $("#about");
//Скрытая строка с полем городов
this.np_tr = $("#np_tr");
//Тег span в который помещаем дату-время
this.cur_time = $("#cur_time");
//Элемента формы с классом set_change, чтобы отслеживать событие onchange
this.sortOption = $(this.form1).find('.set_change');
//Строка с приветствием
this.hello = $("#p_hello");
//Кнопка очистки формы
this.bt_cleen = $("#bt_cleen");
}
}
И, как вы уже заметили, в примере используется такая любимая и привычная библиотека jQuery.
Теперь я хочу связать два элемента. А именно: по выбору значения пункта «Другие регионы» списка <select id=«region»> — чтобы показывалась скрытая строка <tr id=«np_tr»> с другим списком <select id=«np»>. Для этого создайте в классе метод «trigger_select» с входным параметром «element«:
class form1 {
constructor(){
…
}
trigger_select(element){
//при изменении region показываем или скрываем список np
if($(element)[0].id =='region'){
if($(element)[0].value =='other_reg'){
this.np_tr.show();
}else{
this.np_tr.hide();
}
}
}
}
Теперь необходимо вызвать этот метод при событии формы – «onchange». Создайте следующий метод внутри класса:
class form1 {
constructor(){
…
}
trigger_select(element){
…
}
init(){
this.sortOption.on('change',(e)=>{
//console.log(e);
this.trigger_select(e.currentTarget);
});
}
}
Внутри этого метода вызван метод trigger_select. В качестве параметра, передайте «currrentTarget» – свойство объекта «Event», который содержит элемент, привязанный к обработчику события. В console.log(e) это выглядит так:

Теперь вызовите метод init в конструкторе класса:
class form1 {
constructor(){
//Форма регистрации
this.form1 = $("#registrity");
…
//Кнопка очистки формы
this.bt_cleen = $("#bt_cleen");
this.init();
}
…
}
Поздравляю, первый шаг сделан! Теперь проверьте работу в Браузере (Браузер должен поддерживать JS стандарта ECMAScript 2015 (ES6) и выше. И, конечно же, поддерживать HTML5. Необходимо убедиться, что исполнение JS в настройках браузера включено). Откройте index.html в браузере, обновите страницу Ctrl+F5 и пробуйте выбрать пункты регионов. Если все сделано правильно, то должен открыться второй список с населенными пунктами по выбору значения «Другие регионы»

Теперь необходимо расширить возможности формы: при выборе населенного пункта выставлялось его текущее время.
Создайте новый метод в классе, который будет возвращать дату и время с заданным часовым сдвигом:
class form1 {
constructor(){
…
}
addHoursDate(dh){
const inDate = new Date();
inDate.setHours(inDate.getHours()+(1*dh));
const d = inDate.getDate();
const m = inDate.getMonth() + 1;
const y = inDate.getFullYear();
const hh = inDate.getHours();
const mm = inDate.getMinutes();
const ss = inDate.getSeconds();
return '' + (d<=9 ? '0'+d : d) + '.' + (m<=9 ? '0'+m : m) + '.' + y + ' ' + (hh<=9 ? '0'+hh : hh)+ ':' + (mm<=9 ? '0'+mm : mm) + ':' + (ss<=9 ? '0'+ss : ss);
}
trigger_select(element){
…
}
init(){
…
}
}
В качестве входного параметра будет переменная dh, которая будет принимать число со знаком. А возвращать дату и время в формате dd.mm.yyyy hh24:min:ss типа string.
Осталось придумать откуда принимать значения во входную переменную. Для этого можно в «option» со списком населенных пунктов добавить атрибут «data-tmz» со значением часового сдвига от московского времени:
В index.html:
<select id="np" type="select" class="set_change">
<option value="">-</option>
<option value="Архангельск" data-tmz="0" >Архангельск</option>
<option value="Астрахань" data-tmz="1" >Астрахань</option>
<option value="Белгород" data-tmz="1" >Белгород</option>
<option value="Брянск" data-tmz="0" >Брянск</option>
<option value="Великий Новгород" data-tmz="0" >Великий Новгород</option>
<option value="Владивосток" data-tmz="7" >Владивосток</option>
</select>
И теперь добавьте в класс метод, который будет получать атрибут выбранного пункта списка населенных пунктов и возвращать полученное значение:
class form1 {
constructor(){
…
}
addHoursDate(dh){
…
}
getSortOptionAttr(element){
const sortDataOpt = $(element)[0].options[$(element)[0].selectedIndex].dataset.tmz;
return sortDataOpt;
}
trigger_select(element){
…
}
init(){
…
}
}
Поясню для тех, кто еще не знает – почему dataset.tmz, если в примере атрибут полностью называется «data-tmz».
В HTML5 есть поддержка расширения данных элементов с помощью атрибута data-*. Можно добавить в элемент свой собственный атрибут начинающийся с data, а после дефиса именовать своими наименованиями. Например, «data-parent» или «data-go-city», и т.д. В таких атрибутах можно хранить дополнительные значения. В JS доступ к таким атрибутам осуществляется с помощью метода getAttribute(), либо, как в моём случае, – с помощью объекта «dataset». Получить значение свойства объекта «dataset», можно обратившись по части имени, которая стоит после «data-«. В примере – это «tmz«. Продолжим. Входной параметр придуман, теперь осталось придумать: где показывать полученные дату и время. Я прописала тег
<span id="cur_time"></span>
на страницу с формой рядом со списком Регионов, но его можно разместить по своему вкусу, где больше нравится:
В index.html:
<tr><td class="td_left">Регион*:</td><td class="td_right">
<select id="region" type="select" class="set_change" required="required">
<option value="">-</option>
<option value="moscow_mo">Москва и МО</option>
<option value="other_reg">Другие регионы</option>
</select>
<span id="cur_time"></span>
</td></tr>
Чтобы разместить текст в этом теге, сначала создайте метод, который будет размещать любой контент в указанный элемент:
class form1 {
constructor(){
…
}
addHoursDate(dh){
…
}
getSortOptionAttr(element){
…
}
setHTMLElement(element, value){
if(value != ''){
$(element).html(value);
}else{
$(element).html('');
}
}
trigger_select(element){
…
}
init(){
…
}
}
В качестве входных параметров принимаются элемент и значение, которые нужно добавить. Этот метод в дальнейшем будет использоваться много раз.
Сопутствующие методы написаны, теперь в уже имеющийся метод «trigger_select» добавьте строки:
trigger_select(element){
if($(element)[0].id =='region'){
if($(element)[0].value =='other_reg'){
this.np_tr.show();
}else{
this.np_tr.hide();
this.setHTMLElement(this.cur_time,'');
}
//при изменении np устанавливаем текущее время выбранного города
}else if($(element)[0].id == 'np'){
if($(element)[0].value !=''){
this.setHTMLElement(this.cur_time,this.addHoursDate(this.getSortOptionAttr(element)));
}
}
}
Обновите браузер и проверьте результат, выбирая пункты списка «населенные пункты». Должно получиться так:

Теперь создайте «бантики» на форму. Как уже известно, в HTML5 существует встроенная валидация полей. Например, логический атрибут «required» в тегах форм будет проверять заполнение полей. Если поля формы статичные, то такого механизма вполне хватает для простой проверки заполнения и указания обязательных полей формы. А как быть, если необходимо выставлять статус обязательного заполнения, в зависимости от выбора другого поля формы? В этом поможет JS.
Добавьте в метод «trigger_select« строки this.np.attr(‘required’, true) и this.np.attr(‘required’, false):
trigger_select(element){
//при изменении region показываем или скрываем список np
if($(element)[0].id =='region'){
if($(element)[0].value =='other_reg'){
this.np_tr.show();
this.np.attr('required', true);//становится обязательным
}else{
this.np_tr.hide();
this.np.attr('required', false);//теперь необязательное
this.setHTMLElement(this.cur_time,'');
}
//при изменении np устанавливаем текущее время выбранного города
}else if($(element)[0].id == 'np'){
if($(element)[0].value !=''){
this.setHTMLElement(this.cur_time,this.addHoursDate(this.getSortOptionAttr(element)));
}
}
}
Из примера видно, что если выбрать «Другие регионы» и не выбрать населенный пункт, то при нажатии на кнопку «Отправить» выдается ошибка. Но если выбрать «Москва и МО», то ошибки не будет:

По аналогии можно усовершенствовать работу кнопки «Очистить». На статичных формах этот механизм работает хорошо, но он не возвращает динамические поля в исходное состояние. Если попробовать полностью заполнить все поля и нажать кнопку «Очистить», то поле «Населенные пункты» и тег со временем останутся со значениями. Чтобы исправить это, создайте метод, который будет возвращать заданные элементы к исходному состоянию:
class form1 {
constructor(){
…
}
addHoursDate(dh){
…
}
getSortOptionAttr(element){
…
}
setHTMLElement(element, value){
…
}
trigger_select(element){
…
}
trigger_click(element){
if($(element)[0].id =='bt_cleen'){
this.np_tr.hide();
this.setHTMLElement(this.cur_time,'');
}
}
init(){
…
}
}
Теперь необходимо вызвать этот метод в методе init() по событию onclick:
init(){
…
this.bt_cleen.on('click',(e)=>{
this.trigger_click(e.currentTarget);
});
}
По аналогии с событием «Onclick», можно перехватывать событие Onsubmit:
init(){
…
this.form1.on('submit',(e)=>{
this.setHTMLElement($("#p_fio"),this.fio.val());
this.setHTMLElement($("#p_address"),"Адрес: " + this.region.find("option:selected").text() +', '+ this.np.val());
this.setHTMLElement($("#p_email"),"Эл адрес: " + this.email.val());
this.setHTMLElement($("#p_birthdate"),"д/р: " + this.birthdate.val());
this.setHTMLElement($("#p_sex"),"Пол: " + this.t_sex.val());
this.setHTMLElement($("#p_about"),"О себе: " + this.about.val());
return false;
});
}
В указанные элементы страницы вернутся значения полей, а «return false» запретит перегружать страницу при «submit«. Это удобно, если необходимо отправлять данные с помощью ajax на сервер.
Теперь рассмотрим сеттеры и геттеры — это специальные методы класса, которые позволяют, соответственно, получить и присвоить значение определенных свойств объектов. Добавьте в класс сеттер (set) и геттер (get), метод «hello_fio(fio)», и новую переменную в конструктор класса «this.hfio«, которая по умолчанию будет принимать значение входной переменной «helloWorld».
class form1 {
constructor(helloWorld){
…
this.hfio = helloWorld;
}
addHoursDate(dh){
…
}
getSortOptionAttr(element){
…
}
setHTMLElement(element, value){
…
}
trigger_select(element){
…
}
trigger_click(element){
…
}
hello_fio(fio){
console.log('hello_fio: '+`${this.hfio} ${fio}`);
return `${this.hfio} ${fio}`;
}
get HelloFIO(){
console.log('getter: '+this.hfio);
return this.hfio;
}
set HelloFIO(helloWorld){
console.log('setter: '+helloWorld);
this.hfio = helloWorld;
}
init(){
…
}
}
Теперь измените строку, которая будет возвращать в тег <p id=«p_fio»> итоговый текст:
init(){
…
this.form1.on('submit',(e)=>{
this.setHTMLElement($("#p_fio"),this.hello_fio(this.fio.val()));
this.setHTMLElement($("#p_address"),"Адрес: " + this.region.find("option:selected").text() +', '+ this.np.val());
this.setHTMLElement($("#p_email"),"Эл адрес: " + this.email.val());
this.setHTMLElement($("#p_birthdate"),"д/р: " + this.birthdate.val());
this.setHTMLElement($("#p_sex"),"Пол: " + this.t_sex.val());
this.setHTMLElement($("#p_about"),"О себе: " + this.about.val());
return false;
});
}
В части скрипта с вызовом класса добавьте строки:
$(document).ready(function() {
const objform1 = new form1('Доброе утро'); //Передаем значение 'Доброе утро' в переменную класса "helloWorld"
objform1.HelloFIO; //это строка для эксперимента, ее можно удалить
objform1.HelloFIO= 'Добрый день'; //Присваиваем новое значение 'Добрый день' переменной "hfio" объекта
})
Из примера видно, что в переменную класса «helloWorld» передается строка: «Доброе утро», а затем, вызывается сначала геттер (без параметра), а потом сеттер (с параметром и новым значением).
В console.log можно проследить вызовы геттера и сеттера, и значение, которое хранится в переменной «this.hfio» в момент вызовов. А метод «hello_fio(fio)» вернет итоговый текст «Добрый день Olga».

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