Регистрация, вход, заказ плова с доставкой, запись на курс — почти всё, что пользователь «даёт» сайту, он вводит через форму. А пользователи ошибаются: пропускают поля, пишут телефон без кода страны, ставят пробел в конец email. Сегодня вы научитесь читать данные из формы и вежливо объяснять человеку, что нужно исправить, — ещё до отправки.
Читать value и checked у разных полей: текст, число, радиокнопки, чекбоксы, список
Перехватывать отправку формы событием submit и собирать данные через FormData
Пользоваться встроенной валидацией браузера и понимать, чего ей не хватает
Писать простые регулярные выражения и применять их в проверках
Писать свои функции-валидаторы и выводить понятные тексты ошибок прямо под полями
Делать «живую» валидацию: проверять поле, пока человек вводит данные
Как работать с этой страницей
Код на тёмном фоне с кнопкой ▶ Запустить — живой: меняйте его и смотрите результат. В консольных блоках форма создаётся прямо из строки HTML — так мы можем проверить код без ручного ввода. Окна с вкладками index.html / style.css / script.js — мини-страницы: сверху код, снизу превью, в котором можно по-настоящему кликать и печатать. Запуск — Ctrl + Enter. Подсказки к задачам спрятаны: сначала попробуйте сами 15 минут.
01 Форма и её поля: value и checked
Форма в HTML — это тег <form>, внутри которого лежат поля ввода: <input>, <select>, <textarea> и кнопка отправки. Вёрстку вы уже знаете — сегодня нас интересует, как достать из этих полей данные с помощью JavaScript.
Аналогия из жизни
Анкета в банке или бланк заказа в чайхоне: у каждой строки есть название («Имя», «Телефон») и то, что вы вписали. В форме название — это атрибут name, а то, что вписано, — свойство value. Оператор берёт заполненный бланк и читает строку за строкой — ровно так же работает ваш скрипт.
Как найти поле
Каждое поле можно найти через querySelector, но у формы есть удобный путь — коллекция form.elements. В ней поля доступны по атрибуту name:
Читаем поля формы · Ctrl + Enter
<formid="reg"><label>Имя <inputname="fullName"value="Мадина"></label><label>Возраст <inputname="age"type="number"value="19"></label><label>Город
<selectname="city"><optionvalue="dushanbe">Душанбе</option><optionvalue="khujand">Худжанд</option></select></label><label><inputtype="checkbox"name="agree"> Согласен с правилами</label><buttontype="button"id="show">Показать значения</button></form>
Запустите, поменяйте что-нибудь в превью (введите другое имя, поставьте галочку) и нажмите «Показать значения». Вот что и откуда мы читаем:
Поле
Что читаем
Что получим
<input type="text">, email, password, tel
.value
Строка — ровно то, что в поле (с пробелами)
<input type="number">
.value
Тоже строка!"19", не число. Пустое поле — ""
<textarea>
.value
Строка (с переводами строк \n)
<select>
.value
Атрибут value выбранного <option>, а не его видимый текст
<input type="checkbox">
.checked
true или false
Группа <input type="radio"> с одним name
form.elements.имя.value
value выбранной кнопки (или "", если ничего не выбрано)
Главная ловушка: value — всегда строка
Даже у type="number" значение приходит строкой. Это та самая история с приведением типов из урока 2:
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<input name="plov" type="number" value="40">
<input name="tea" type="number" value="10">`;
const plov = document.querySelector("[name=plov]").value;
const tea = document.querySelector("[name=tea]").value;
console.log(plov + tea); // "4010" — склеились как строки!
console.log(Number(plov) + Number(tea)); // 50 — сначала превращаем в числа
console.log(document.querySelector("[name=plov]").valueAsNumber); // 40 — число сразу
Частые ошибки
1) Читать у чекбокса .value вместо .checked — вы всегда получите строку "on", даже если галочки нет. 2) Забыть .value и вывести сам элемент: console.log(form.elements.fullName) покажет тег, а не текст. 3) Сложить два числовых поля через + — получится склейка строк.
Атрибуты name, label и type
name — «имя поля». Через него мы находим поле в form.elements, и именно по нему данные попадают в FormData (раздел 3). Без name поле «невидимо» для формы.
<label> — подпись. Оберните в неё поле или свяжите через for и id: тогда клик по тексту ставит курсор в поле, а экранные читалки понимают, что за поле перед ними.
type — подсказывает браузеру, какую клавиатуру показать на телефоне (tel — цифровую, email — с «@») и какие проверки включить.
В консольных задачах формы создаются из строки HTML. Ваша работа — прочитать нужные значения.
ЗАДАЧА 1.1
Анкета студента
лёгкая
В форме уже есть значения. Выведите их тремя строками в формате, как в примере: имя, город и курс.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="fullName" value="Мадина Каримова">
<input name="city" value="Душанбе">
<input name="course" value="Frontend + AI">
</form>`;
const form = document.querySelector("form");
// выведите три строки через console.log и form.elements
Ожидаемый результат
Имя: Мадина Каримова
Город: Душанбе
Курс: Frontend + AI
Подсказка 1
Поле находим так: form.elements.fullName — по значению атрибута name.
Подсказка 2
Значение — это свойство .value. Склейте строку: console.log("Имя: " + form.elements.fullName.value);
ЗАДАЧА 1.2
Счёт в чайхоне
лёгкая
Кассир вводит стоимость плова и чая в числовые поля. Код должен вывести итог и итог со скидкой 10%, но сейчас выводит ерунду вроде 4010. Исправьте чтение значений, не трогая строки с console.log.
Для списка и для группы радиокнопок подойдёт form.elements.имя.value. Для чекбоксов — .checked.
Подсказка 2
Радиокнопки с одинаковым name образуют группу: form.elements.format.value вернёт значение выбранной ("online").
02 Отправка формы: событие submit
Когда человек нажимает кнопку «Отправить» или жмёт Enter в текстовом поле, форма генерирует событие submit. Если ничего не делать, браузер отправит данные на сервер и перезагрузит страницу — все введённые данные и вся ваша JS-логика пропадут. Поэтому первая строка любого обработчика отправки — event.preventDefault() (вы знаете это из урока 9).
Вручную «стреляет» событием submit. Так мы имитируем нажатие «Отправить» в примерах и задачах: учебная песочница блокирует настоящую отправку форм, поэтому обычный form.requestSubmit() (он нажимает кнопку сам) здесь не сработает. На обычном сайте вы кликаете по кнопке или вызываете именно requestSubmit().
<button type="button">
Кнопка внутри формы по умолчанию отправляет её. Если кнопка нужна для другого (например, «показать пароль»), пишите type="button".
novalidate
Атрибут формы: отключает встроенную проверку браузера, чтобы работала ваша собственная (раздел 4).
Типичный баг: читаем значение слишком рано
Значение поля меняется, пока человек печатает. Поэтому читать его надо в момент события, внутри обработчика. Если прочитать один раз в начале скрипта, вы получите то, что было в поле при загрузке страницы (чаще всего — пустую строку).
Баг: пустой email · Ctrl + Enter
document.body.innerHTML = `<form><input name="email"></form>`;
const form = document.querySelector("form");
const email = form.elements.email.value; // ✖ прочитали сразу — там ещё пусто
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("email:", "[" + email + "]");
});
form.elements.email.value = "madina@mail.tj"; // «пользователь» вписал адрес
form.dispatchEvent(newEvent("submit", { cancelable: true })); // → email: []
Аналогия из жизни
Официант в чайхоне записывает заказ не тогда, когда вы только сели за стол, а когда вы сказали «можно заказывать». Значения в форме читаем так же — в момент отправки, а не заранее.
form.addEventListener("submit", (event) => { ... }) — а первой строкой внутри event.preventDefault().
Подсказка 2
Значения читайте внутри обработчика: form.elements.fullName.value и form.elements.phone.value.
ЗАДАЧА 2.2
Найди баг: город пропал
лёгкая
Пользователь вписал «Худжанд», но обработчик печатает пустой город. Найдите причину и исправьте. Две последние строки скрипта (заполнение и отправка) менять нельзя.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `<form><input name="city"></form>`;
const form = document.querySelector("form");
const city = form.elements.city.value;
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Город:", city);
});
form.elements.city.value = "Худжанд"; // не менять
form.dispatchEvent(newEvent("submit", { cancelable: true })); // не менять
Ожидаемый результат
Город: Худжанд
Подсказка 1
Когда выполняется строка const city = ...? До того, как значение поля поменяли.
Подсказка 2
Перенесите чтение значения внутрь обработчика submit.
ЗАДАЧА 2.3
Форма входа
средняя
Напишите обработчик формы входа. Логин нужно очистить от пробелов по краям (trim()). Если логин или пароль пустые — выведите Ошибка: заполните все поля. Иначе выведите Вход выполнен: логин, сбросьте форму и сообщите, пустое ли после этого поле логина.
Ошибка: заполните все поля
Ошибка: заполните все поля
Вход выполнен: madina
Логин после сброса: пусто
Подсказка 1
Порядок: preventDefault → прочитать и trim() логин → прочитать пароль → проверка if (!login || !password) → return после сообщения об ошибке.
Подсказка 2
После успешного входа: form.reset(), затем console.log("Логин после сброса:", form.elements.login.value === "" ? "пусто" : "заполнено").
03FormData: собираем все поля разом
Если в форме 10 полей, читать их по одному через .value — скучно и легко что-то забыть. Для этого в браузере есть встроенный объект FormData: вы отдаёте ему форму, а он сам обходит все поля с атрибутом name и собирает их «имя → значение».
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="fullName" value="Мадина">
<select name="city">
<option value="dushanbe" selected>Душанбе</option>
<option value="khujand">Худжанд</option>
</select>
<input type="checkbox" name="skills" value="css" checked>
<input type="checkbox" name="skills" value="js" checked>
<input type="checkbox" name="skills" value="git">
<input type="checkbox" name="agree" checked>
<input type="checkbox" name="news">
</form>`;
const form = document.querySelector("form");
const data = newFormData(form);
console.log(data.get("fullName")); // Мадина — одно значение по имени
console.log(data.getAll("skills")); // ["css", "js"] — все значения одного имени
console.log(data.has("news")); // false — чекбокс без галочки в FormData не попадает
console.log(data.get("agree")); // "on" — у чекбокса без атрибута value значение "on"for (const [key, value] of data) { // FormData можно перебрать как пары «ключ, значение»
console.log(key, "=", value);
}
Из FormData легко получить привычный объект — он пригодится, чтобы сохранить данные или показать их пользователю (упакованный в JSON, как на уроке 7):
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="fullName" value="Мадина">
<input name="age" type="number" value="19">
</form>`;
const data = newFormData(document.querySelector("form"));
const obj = Object.fromEntries(data); // { fullName: "Мадина", age: "19" }
console.log(JSON.stringify(obj));
console.log(typeof obj.age); // string — все значения по-прежнему строки!
Аналогия из жизни
Это как почтовый курьер, который забирает из офиса все заполненные бланки одним пакетом. Но в пакет он берёт только бланки с подписью (name) и только те, которые заполнены. Поле без name — безымянный бланк, его курьер не возьмёт.
Что не попадает в FormData
поля без атрибута name;
поля с атрибутом disabled (для «только чтения» используйте readonly);
чекбоксы и радиокнопки без галочки — их ключа в данных просто нет. Если нужно false, берите checked напрямую: form.elements.news.checked.
А ещё Object.fromEntries оставляет только последнее значение для повторяющихся имён (skills), поэтому для групп чекбоксов используйте data.getAll("skills").
Позже, когда мы дойдём до работы с сервером, этот же объект FormData можно будет отправить на сервер как есть. А пока наша цель — собрать данные и убедиться, что они правильные.
const data = new FormData(form); — форма передаётся в скобках.
Подсказка 2
data.get("email") вернёт значение поля с name="email".
ЗАДАЧА 3.2
Почини форму: пропавшие поля
средняя
Из трёх полей в FormData попало только одно. Исправьте HTML формы (не JS), чтобы в итоговом объекте было три поля: fullName, phone и agree. Телефон должен остаться нередактируемым для пользователя, но попадать в данные.
Первому полю не хватает одного атрибута, который даёт ему «имя» в данных формы.
Подсказка 2
У второго поля замените disabled на readonly: оно по-прежнему нередактируемое, но уже участвует в отправке.
ЗАДАЧА 3.3
Функция readForm
сложная
Напишите функцию readForm(form), которая возвращает объект с такими полями в таком порядке: fullName (строка), age (число), skills (массив отмеченных навыков), agree и news (true/false).
Начните с const data = new FormData(form);. Строку и массив берите оттуда: data.get(...) и data.getAll(...).
Подсказка 2
Возраст — Number(data.get("age")). Чекбоксы в FormData могут отсутствовать, поэтому для них надёжнее form.elements.agree.checked.
04 Встроенная валидация браузера
Валидация — это проверка, что данные подходят: поле не пустое, email похож на email, возраст в разумных пределах. У браузера уже есть готовая проверка — её включают атрибуты HTML, а JavaScript может её опросить.
Атрибут
Что проверяет
Пример
required
Поле обязательно
<input required>
type="email", url
Формат адреса (есть @, есть домен)
<input type="email">
min, max
Границы числа (и даты)
<input type="number" min="16" max="70">
minlength, maxlength
Длина текста
<input minlength="2" maxlength="30">
pattern
Текст подходит под шаблон (регулярное выражение — раздел 5)
<input pattern="[0-9]{4}">
Попробуйте прямо в превью: оставьте поле пустым или впишите «ali@» и нажмите «Отправить». Браузер сам покажет всплывающую подсказку и не пропустит форму дальше.
form {display: grid; gap: 10px; max-width: 320px; }
label {display: grid; gap: 4px; font-size: 14px; }
input, button {padding: 6px8px; font: inherit; }/* :user-invalid подсвечивает поле только ПОСЛЕ того, как пользователь с ним поработал */
input:user-invalid {border: 2px solid #d33; background: #fff0f0; }
input:user-valid {border: 2px solid #2a9d5c; }
const form = document.querySelector("#f");
// Песочница блокирует настоящую отправку, поэтому «Отправить» обрабатываем по клику.// На обычной странице вместо этого пишут form.addEventListener("submit", ...)
form.querySelector("button").addEventListener("click", (event) => {
event.preventDefault();
if (form.reportValidity()) { // показывает подсказки браузера, если что-то не так
console.log("Форма прошла проверку браузера");
}
});
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Спросим у браузера из JavaScript
У каждого поля есть методы и свойства, которые сообщают результат встроенной проверки:
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="email" type="email" required value="madina@">
</form>`;
const email = document.querySelector("[name=email]");
console.log(email.checkValidity()); // false — поле не проходит проверку
console.log(email.validity.valid); // false — то же самое, но как свойство
console.log(email.validity.typeMismatch); // true — «тип не тот»: это не email
console.log(email.validity.valueMissing); // false — поле не пустое
email.value = "madina@mail.tj";
console.log(email.checkValidity()); // true — теперь всё хорошо
checkValidity()
Возвращает true / false. У формы проверяет все её поля сразу.
reportValidity()
То же, но ещё и показывает пользователю всплывающую подсказку браузера.
validity
Объект с флагами причин: valueMissing (пусто при required), typeMismatch (не email/url), patternMismatch (не подошёл pattern), rangeUnderflow / rangeOverflow (меньше min / больше max), tooShort / tooLong, customError.
validationMessage
Текст ошибки на языке браузера пользователя. У одного человека он русский, у другого английский — управлять им нельзя.
setCustomValidity("текст")
Вручную объявляет поле невалидным с вашим сообщением. Пустая строка "" снимает ошибку.
Встроенная валидация — как охранник на входе в торговый центр: он смотрит на очевидное (есть ли билет, не пустой ли пропуск), но не знает правил вашего магазина. Он не скажет «Вам нужен именно таджикский номер с кодом +992» и не объяснит причину отказа на понятном вам языке и в нужном месте страницы. Для этого нужна собственная проверка.
Чего не хватает встроенной валидации
Тексты ошибок — на языке и в стиле браузера, их не оформить по дизайну сайта.
Один пузырёк за раз — человек видит только первую ошибку, а не все сразу.
Сложные правила — «пароли совпадают», «телефон только таджикский» требуют своего кода.
Живая проверка по ходу ввода — «красиво» без своего JavaScript не получится.
Нюансы
1) minlength и maxlength проверяются только когда значение вводит человек. Если записать input.value = "a" из JavaScript, флаг tooShort не сработает. 2) Все проверки браузера и ваш JS на странице легко обойти (достаточно открыть DevTools), поэтому на настоящем сайте данные проверяют ещё и на сервере. Проверка в браузере — это удобство для человека, а не защита.
Поэтому на практике делают так: ставят базовые атрибуты (required, type) ради подсказок мобильной клавиатуры, а саму проверку и тексты ошибок пишут в JavaScript и отключают браузерные пузырьки атрибутом novalidate на форме:
Опрашиваем браузер и учимся переключаться на собственную проверку.
ЗАДАЧА 4.1
Кто невалиден
лёгкая
В форме четыре поля. Пройдите по form.elements и выведите одной строкой имена полей, которые не проходят проверку браузера, через запятую.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="fullName" required value="">
<input name="email" type="email" value="madina@">
<input name="age" type="number" min="16" max="70" value="15">
<input name="phone" pattern="\\+992[0-9]{9}" value="+992927771234">
</form>`;
const form = document.querySelector("form");
const bad = [];
// пройдите по form.elements и наполните массив bad именами невалидных полей
console.log("Невалидные поля: " + bad.join(", "));
Ожидаемый результат
Невалидные поля: fullName, email, age
Подсказка 1
for (const field of form.elements) { ... } — перебирает все поля формы по очереди.
Подсказка 2
Внутри цикла: if (!field.checkValidity()) bad.push(field.name);
ЗАДАЧА 4.2
Почему невалидно
средняя
Напишите функцию reasonOf(input). Она смотрит на флаги validity и возвращает причину: "пусто" (valueMissing), "не email" (typeMismatch), "слишком мало" (rangeUnderflow), "не по шаблону" (patternMismatch) или "ок", если поле валидно.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<input id="a" required value="">
<input id="b" type="email" value="ali.mail.tj">
<input id="c" type="number" min="16" value="12">
<input id="d" pattern="[0-9]{4}" value="12ab">
<input id="e" required value="всё хорошо">`;
functionreasonOf(input) {
// верните одну из пяти строк
}
for (const id of ["a", "b", "c", "d", "e"]) {
console.log(id + ": " + reasonOf(document.getElementById(id)));
}
Ожидаемый результат
a: пусто
b: не email
c: слишком мало
d: не по шаблону
e: ок
Подсказка 1
Флаги лежат в input.validity. Проверяйте по очереди: if (input.validity.valueMissing) return "пусто";
Подсказка 2
После четырёх проверок с ранним return последняя строка функции — return "ок";
ЗАДАЧА 4.3
Валидна ли форма целиком
лёгкая
В форме есть обязательное поле «Имя» и корректный email. Выведите результат checkValidity()формы до заполнения имени, затем впишите имя «Мадина» и выведите результат ещё раз.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `
<form>
<input name="fullName" required>
<input name="email" type="email" value="madina@mail.tj">
</form>`;
const form = document.querySelector("form");
// 1) выведите form.checkValidity()// 2) впишите имя в поле fullName// 3) выведите form.checkValidity() ещё раз
Ожидаемый результат
false
true
Подсказка 1
У самой формы есть тот же метод, что и у поля: form.checkValidity() — он проверяет все поля сразу.
Подсказка 2
Значение задаётся так: form.elements.fullName.value = "Мадина";
05 Регулярные выражения для новичков
Регулярное выражение (regex, «регэксп») — это короткий шаблон, который описывает, как должна выглядеть строка. «Только цифры», «начинается с +992 и дальше девять цифр», «латинские буквы и подчёркивание» — всё это записывается одной строчкой. Для проверки форм это незаменимый инструмент.
Аналогия из жизни
Портной снимает мерки и вырезает ткань по лекалу: деталь либо точно ложится на лекало, либо нет. Регулярное выражение — лекало для текста. Вы описываете форму один раз, а компьютер прикладывает к ней любую строку и отвечает «подходит» или «не подходит».
Можно редактировать · Ctrl + Enter
const onlyDigits = /^\d+$/; // между слэшами — шаблон
console.log(onlyDigits.test("12345")); // true — подходит
console.log(onlyDigits.test("12a45")); // false — есть буква
console.log(onlyDigits.test("")); // false — пусто (нужна хотя бы одна цифра)
Регулярное выражение записывается между двумя слэшами /.../. Метод .test(строка) возвращает true, если строка подходит под шаблон, иначе — false. Теперь разберём «буквы» этого языка.
Алфавит регулярок: всего несколько символов
Запись
Значение
Пример
abc
Обычные символы значат сами себя
/tj/ найдёт «tj»
\d
Одна цифра (0–9)
/\d/ подходит к «a1»
\D
Один символ, который НЕ цифра
/\D/ подходит к «a1»
\s
Пробельный символ: пробел, таб, перевод строки
/\s/ подходит к «a b»
.
Любой один символ
/a.c/ подходит к «abc», «a-c»
[abc]
Один символ из списка
/[аеи]/ — одна из трёх гласных
[a-z], [0-9]
Один символ из диапазона
/[a-zA-Z]/ — любая латинская буква
[^abc]
Один символ, которого НЕТ в списке
/[^\s@]/ — не пробел и не «@»
^ и $
Начало и конец всей строки
/^\d+$/ — вся строка из цифр
+
Предыдущее — один раз или больше
/\d+/ — число из любого количества цифр
*
Предыдущее — ноль раз или больше
/ab*/ — «a», «ab», «abbb»
?
Предыдущее — один раз или ни разу
/\+?\d+/ — плюс не обязателен
{4}, {3,16}
Ровно 4 раза / от 3 до 16 раз
/\d{9}/ — девять цифр
\+, \.
Слэш перед спецсимволом: ищем сам символ
\. — именно точка, а не «любой символ»
Читаем регулярку по частям
Номер телефона в Таджикистане: +992 и девять цифр. Разберём шаблон слева направо:
^\+992\d{9}$
/^\+992\d{9}$/ — строка начинается с «+992», дальше ровно девять цифр, и больше ничего
Можно редактировать · Ctrl + Enter
const phone = /^\+992\d{9}$/;
console.log(phone.test("+992927771234")); // true
console.log(phone.test("+99292777123")); // false — только 8 цифр
console.log(phone.test("+9929277712345")); // false — 10 цифр, лишняя
console.log(phone.test("927771234")); // false — нет кода страны
Якоря ^ и $: самая частая ошибка
Без ^ и $ регулярка ищет подходящий кусочек внутри строки — и пропускает мусор вокруг:
Баг: забыли якоря · Ctrl + Enter
const bad = /\d{4}/; // «где-то в строке есть 4 цифры подряд»const good = /^\d{4}$/; // «вся строка — ровно 4 цифры»
console.log(bad.test("abc1234xyz")); // true ← ✖ пропустили мусор
console.log(good.test("abc1234xyz")); // false ← так правильно
console.log(good.test("1234")); // true
Флаги и замена: чистим ввод
После закрывающего слэша можно ставить флаги: i — не различать большие и маленькие буквы, g — искать все совпадения, а не только первое. С методом replace это удобный способ почистить телефон от пробелов и дефисов:
Можно редактировать · Ctrl + Enter
const raw = "+992 92 777-12-34";
const clean = raw.replace(/[\s-]/g, ""); // убираем все пробелы и дефисы
console.log(clean); // +992927771234
console.log(/^tj$/i.test("TJ")); // true — регистр не важен из-за флага i
console.log("a1b22c333".replace(/\D/g, "")); // 122333 — оставили только цифры
Готовые шаблоны для форм
Что проверяем
Регулярка
Как читать
Телефон +992
/^\+992\d{9}$/
«+992» и ровно 9 цифр
Логин
/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/
первая — латинская буква, дальше 2–15 букв, цифр или «_»: всего 3–16 символов
Email (простой)
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
что-то без пробелов и «@», затем «@», затем снова такое же, затем точка и ещё что-то
Есть цифра
/\d/
где-то в строке встречается цифра (якоря не нужны!)
Есть заглавная буква
/[A-Z]/
где-то есть латинская заглавная
Имя на кириллице
/^[А-Яа-яЁё\s-]+$/
только русские буквы (Ё и ё нужно добавить отдельно), пробелы и дефис
Что нужно знать про регулярки
Якоря. Проверяете «вся строка такая» — ставьте ^ и $. Ищете «содержит» — не ставьте.
Не угадывайте, проверяйте. Регулярка — это код. Прогоните её на 5–10 примерах: подходящих и нет (в разделе про ИИ мы сделаем это системно).
Не усложняйте. Идеального выражения для email не существует, а «идеальные» занимают три строки, и никто их не понимает. Достаточно простой проверки формата. Что адрес реально существует, проверяют письмом с кодом, а не регуляркой.
Кириллица и \w. Шаблон \w (буквы, цифры, «_») понимает только латиницу. Для русских и таджикских букв пишите диапазоны явно и проверяйте на реальных именах.
Сейчас шаблон ищет шесть цифр где-то внутри строки. Нужно сказать, что строка состоит только из них.
Подсказка 2
Добавьте якоря: /^\d{6}$/
ЗАДАЧА 5.5
Сила пароля
сложная
Напишите passwordStrength(p). Пароль получает по баллу за каждое выполненное условие: длина от 8 символов; есть строчная латинская буква; есть заглавная латинская буква; есть цифра; есть спецсимвол из набора !@#$%^&*. Результат: 0–2 балла — слабый, 3–4 — средний, 5 — сильный.
Можно редактировать · Ctrl + Enter
functionpasswordStrength(p) {
let points = 0;
// добавляйте к points по 1 за каждое условие// верните "слабый", "средний" или "сильный"
}
for (const p of ["", "abc", "12345678", "madina123", "Madina2024", "Madina2024!"]) {
console.log("[" + p + "] → " + passwordStrength(p));
}
Ожидаемый результат
[] → слабый
[abc] → слабый
[12345678] → слабый
[madina123] → средний
[Madina2024] → средний
[Madina2024!] → сильный
Подсказка 1
Пять отдельных if: if (p.length >= 8) points++;, if (/[a-z]/.test(p)) points++; и так далее. Здесь якоря не нужны — мы ищем «содержит».
Подсказка 2
Спецсимволы: /[!@#$%^&*]/. В конце: if (points <= 2) return "слабый"; if (points <= 4) return "средний"; return "сильный";
06 Своя валидация: функции-валидаторы и тексты ошибок
Теперь у нас есть всё, чтобы написать собственную проверку. Рецепт простой и работает для любой формы:
Один валидатор на поле. Это функция, которая принимает значение и возвращает текст ошибки — или пустую строку "", если всё хорошо.
Собираем в объект.{ fullName: validateName, email: validateEmail, ... } — по ключу видно, какой валидатор к какому полю.
Показываем ошибки. Текст — в <span> под полем, а само поле помечаем красной рамкой и атрибутом aria-invalid.
Не отправляем, пока есть хоть одна ошибка.
Шаг 1: валидатор — это функция «значение → текст ошибки»
Функция только проверяет и ничего не знает про страницу. Поэтому её легко тестировать (мы только что вызвали её с тремя примерами) и легко использовать повторно — на любой странице и с любым дизайном. Правило «функция считает, другая функция показывает» вы уже встречали на уроке 5.
Аналогия из жизни
Контролёр на входе в метро. Он не бежит за вами — он стоит у турникета и смотрит на билет. Если что-то не так, он называет причину («приложите карту», «не хватает денег»). Валидатор — тот же контролёр: на выходе либо «проходите» (""), либо понятная причина отказа.
Какие тексты ошибок писать
Плохо
«Ошибка!», «Неверное значение», «Invalid input», «Поле заполнено неверно». Человек не понимает, что не так и как исправить.
Хорошо
«Введите номер в формате +992 и 9 цифр», «Пароль — минимум 8 символов», «Пароли не совпадают». Сказано, чего ждём, и сразу виден пример.
Говорите на «вы» и спокойно: не «Ты ввёл неправильно», а «Проверьте, пожалуйста, email».
Называйте правило, а не только факт ошибки: «минимум 2 буквы» полезнее, чем «слишком коротко».
Показывайте ошибку рядом с полем, а не одним окном alert вверху страницы.
Пустое обязательное поле и неверный формат — это разные сообщения: «Введите email» и «Неверный формат email».
Шаг 2: проверяем все поля сразу
Когда валидаторов несколько, удобно держать их в объекте и пройти циклом. На выходе — объект ошибок: в нём есть ключи только у невалидных полей. Пустой объект — всё хорошо.
Каждое поле получает «место для ошибки» — пустой <span> рядом. При ошибке мы пишем в него текст, при её отсутствии — стираем. Всегда используйте textContent, а не innerHTML: ввод пользователя нельзя вставлять в страницу как HTML.
const form = document.querySelector("#f");
const validators = {
fullName: (v) => (v.trim().length < 2 ? "Введите имя (минимум 2 буквы)" : ""),
age: (v) => (Number(v) >= 16 ? "" : "Возраст — число от 16"),
};
functionshowError(name, message) {
const input = form.elements[name];
const box = form.querySelector(`[data-error="${name}"]`);
box.textContent = message; // пустая строка стирает ошибку
input.classList.toggle("invalid", message !==""); // красная рамка
input.setAttribute("aria-invalid", message ? "true" : "false"); // для читалок экрана
}
form.addEventListener("submit", (event) => {
event.preventDefault();
let firstBad = null;
for (const name in validators) {
const message = validators[name](form.elements[name].value);
showError(name, message);
if (message && !firstBad) firstBad = form.elements[name];
}
if (firstBad) {
firstBad.focus(); // ставим курсор в первое ошибочное полеreturn; // дальше не идём — форма не отправляется
}
console.log("Отправлено:", JSON.stringify(Object.fromEntries(newFormData(form))));
});
// Песочница блокирует настоящую отправку формы, поэтому клик по кнопке превращаем в submit вручную// (на обычной странице эта вставка не нужна)
form.querySelector("button").addEventListener("click", (event) => {
event.preventDefault();
form.dispatchEvent(newEvent("submit", { cancelable: true }));
});
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Запустите, нажмите «Отправить» с пустыми полями, затем исправьте и отправьте снова. Обратите внимание на три детали: ошибки показываются сразу у всех полей, курсор встаёт в первое «плохое» поле, а у исправленного поля ошибка пропадает.
Типичные ошибки
Забыли trim() — « » (пробелы) считается непустым именем.
Сравниваем строку как число — "5" < "16" сравнит строки по символам. Сначала Number(...).
Не стираем старую ошибку — человек исправил поле, а красный текст остался. Поэтому showError всегда вызывается и для валидных полей — с пустой строкой.
Показываем ввод через innerHTML — открывает дыру для XSS. Только textContent.
Проверка только в браузере — для настоящего сайта нужна ещё и серверная.
Пишем валидаторы — от одного поля до целой формы. Выводимые тексты ошибок сверяются буква в букву, поэтому копируйте их из условия.
ЗАДАЧА 6.1
Валидатор имени
лёгкая
Напишите validateName(value). Пробелы по краям игнорируются. Пустое имя → Введите имя. Короче двух символов → Имя слишком короткое. Иначе — пустая строка.
Можно редактировать · Ctrl + Enter
functionvalidateName(value) {
// верните "" или текст ошибки
}
for (const v of ["", " ", "А", "Мадина", " Фарход "]) {
console.log("[" + validateName(v) + "]");
}
Ожидаемый результат
[Введите имя]
[Введите имя]
[Имя слишком короткое]
[]
[]
Подсказка 1
Сначала const name = value.trim(); — дальше работаем с очищенной строкой.
Подсказка 2
Две проверки с ранним return: if (name === "") return "Введите имя"; и if (name.length < 2) return .... В конце — return "";
ЗАДАЧА 6.2
Валидатор возраста
средняя
Напишите validateAge(value) — значение приходит строкой из поля. Правила в таком порядке: пустое (после trim) → Введите возраст; не число → Возраст — это число; меньше 16 → Курс с 16 лет; больше 70 → Проверьте возраст; иначе "".
Можно редактировать · Ctrl + Enter
functionvalidateAge(value) {
// не забудьте: Number("") равно 0, поэтому пустоту проверяем отдельно и первой
}
for (const v of ["", "abc", "15", "24", "99", " 30 "]) {
console.log("[" + validateAge(v) + "]");
}
Ожидаемый результат
[Введите возраст]
[Возраст — это число]
[Курс с 16 лет]
[]
[Проверьте возраст]
[]
Подсказка 1
Порядок: const text = value.trim(); → пусто → const age = Number(text); → Number.isNaN(age) → границы.
Подсказка 2
Number("abc") даёт NaN, а проверить его можно только через Number.isNaN(age) (сравнение age === NaN всегда false).
ЗАДАЧА 6.3
Все проблемы пароля сразу
средняя
Напишите validatePassword(password, login), которая возвращает массив всех найденных проблем (в этом порядке): Минимум 8 символов; Добавьте цифру (нет ни одной цифры); Без пробелов (есть пробельный символ); Не совпадает с логином (пароль равен логину без учёта регистра). Если проблем нет — пустой массив.
Можно редактировать · Ctrl + Enter
functionvalidatePassword(password, login) {
const problems = [];
// добавляйте тексты в problems через pushreturn problems;
}
const tests = [["abc", "madina"], ["Madina", "madina"], ["pass word 1", "x"], ["qwerty123", "madina"]];
for (const [p, l] of tests) {
console.log(validatePassword(p, l).join(" | ") ||"ок");
}
Ожидаемый результат
Минимум 8 символов | Добавьте цифру
Минимум 8 символов | Добавьте цифру | Не совпадает с логином
Без пробелов
ок
Подсказка 1
Четыре независимых if без return: каждый при необходимости добавляет свой текст в массив.
Три валидатора уже написаны. Напишите validateForm(data): она вызывает нужный валидатор для каждого поля из объекта validators и возвращает объект ошибок — с ключами только для невалидных полей.
{}
{"fullName":"Введите имя","email":"Неверный email"}
{"email":"Введите email","phone":"Телефон: +992 и 9 цифр"}
Подсказка 1
Создайте const errors = {};, пройдите циклом for (const field in validators) и в конце верните errors.
Подсказка 2
Внутри цикла: const message = validators[field](data[field]); и if (message) errors[field] = message;
ЗАДАЧА 6.5
Ошибки на странице
сложная
В скрипте не хватает двух частей. Напишите showError(name, message): она пишет текст в span[data-error="имя"], ставит или снимает класс invalid у поля и выставляет aria-invalid в "true" или "false". Затем напишите обработчик submit: отмена отправки, проверка всех полей через validators, показ ошибок и при успехе console.log("Отправлено"). Блок «проверка» внизу не меняйте.
const form = document.querySelector("#f");
const validators = {
fullName: (v) => (v.trim().length < 2 ? "Введите имя (минимум 2 буквы)" : ""),
email: (v) => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim()) ? "" : "Введите email, например madina@mail.tj"),
};
functionshowError(name, message) {
// текст в span, класс invalid, aria-invalid
}
form.addEventListener("submit", (event) => {
// preventDefault, проверка всех полей, showError, "Отправлено" при успехе
});
// ---- проверка (не менять) ----// Песочница блокирует настоящую отправку формы, поэтому клик по кнопке превращаем в submit вручную// (на обычной странице эта вставка не нужна)
form.querySelector("button").addEventListener("click", (event) => {
event.preventDefault();
form.dispatchEvent(newEvent("submit", { cancelable: true }));
});
functionreport(name) {
const input = form.elements[name];
const text = form.querySelector(`[data-error="${name}"]`).textContent;
console.log(`${name}: ${text || "ok"} | aria-invalid=${input.getAttribute("aria-invalid")} | invalid=${input.classList.contains("invalid")}`);
}
functiontryWith(fullName, email) {
form.elements.fullName.value = fullName;
form.elements.email.value = email;
form.dispatchEvent(newEvent("submit", { cancelable: true }));
report("fullName");
report("email");
}
tryWith(" ", "ali@mail.tj");
tryWith("Али", "ali@");
tryWith("Али", "ali@mail.tj");
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
fullName: Введите имя (минимум 2 буквы) | aria-invalid=true | invalid=true
email: ok | aria-invalid=false | invalid=false
fullName: ok | aria-invalid=false | invalid=false
email: Введите email, например madina@mail.tj | aria-invalid=true | invalid=true
Отправлено
fullName: ok | aria-invalid=false | invalid=false
email: ok | aria-invalid=false | invalid=false
Подсказка 1
showError: найдите поле form.elements[name] и подпись form.querySelector(`[data-error="${name}"]`); запишите textContent; classList.toggle("invalid", message !== ""); setAttribute("aria-invalid", message ? "true" : "false").
Подсказка 2
В обработчике: event.preventDefault(), let ok = true;, цикл for (const name in validators) — внутри считаем сообщение, вызываем showError (даже если сообщение пустое!) и при ошибке ставим ok = false. После цикла: if (ok) console.log("Отправлено");
07 Живая валидация: проверяем, пока человек печатает
Проверка только при нажатии «Отправить» работает, но неудобно: человек заполнил десять полей и узнаёт об ошибке в самом первом. Живая валидация подсказывает сразу. Для неё нужны три события:
input
Срабатывает на каждое изменение значения: нажали клавишу, вставили текст, стёрли символ.
change
Срабатывает, когда значение поменялось и поле потеряло фокус (для чекбокса и списка — сразу при выборе).
blur
Поле потеряло фокус — человек ушёл в другое поле. Не всплывает (в отличие от focusout).
Аналогия из жизни
Хороший официант в чайхоне не перебивает вас на полуслове: «Нет, плова уже нет!» Он ждёт, пока вы закончите говорить, и только потом поправляет. Так и форма: не ругайтесь на поле, пока человек не закончил его вводить, иначе первая же буква email вызовет красное «Неверный email».
Правило «тронуто / не тронуто» (touched)
Пока человек не покидал поле — ошибку не показываем, даже если значение пока неверное.
Когда сработал blur — поле считается «тронутым»: проверяем и показываем ошибку.
С этого момента на каждый input перепроверяем: как только человек исправил, ошибка исчезает сразу.
При нажатии «Отправить» «трогаем» все поля сразу и показываем все ошибки.
Живая проверка email и пароля · Ctrl + Enter
<formid="f" novalidate><divclass="field"><labelfor="email">Email</label><inputid="email"name="email"type="email"autocomplete="off"><spanclass="error"id="email-error"></span></div><divclass="field"><labelfor="pass">Пароль</label><divclass="row"><inputid="pass"name="pass"type="password"maxlength="20"><buttontype="button"id="eye">Показать</button></div><spanclass="hint"id="pass-hint">Введено: 0 из 20</span></div></form>
const email = document.querySelector("#email");
const emailError = document.querySelector("#email-error");
let touched = false;
functionvalidateEmail() {
const value = email.value.trim();
let message = "";
if (value ==="") message = "Введите email";
elseif (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) message = "Похоже, в адресе ошибка";
emailError.textContent = message;
email.classList.toggle("invalid", message !=="");
}
email.addEventListener("blur", () => {
touched = true; // человек ушёл из поля — теперь можно ругатьсяvalidateEmail();
});
email.addEventListener("input", () => {
if (touched) validateEmail(); // после первого «ухода» проверяем на каждую букву
});
// пароль: счётчик символов и кнопка «показать»const pass = document.querySelector("#pass");
const hint = document.querySelector("#pass-hint");
pass.addEventListener("input", () => {
hint.textContent = `Введено: ${pass.value.length} из ${pass.maxLength}`;
});
document.querySelector("#eye").addEventListener("click", (event) => {
const hidden = pass.type ==="password";
pass.type = hidden ? "text" : "password";
event.target.textContent = hidden ? "Скрыть" : "Показать";
});
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Попробуйте: наберите в email «ali» — ничего не происходит. Нажмите Tab — появилась ошибка. Допишите «@mail.tj» — ошибка исчезла прямо во время набора.
Сравнение двух полей: пароль и повтор
Правило «пароли совпадают» зависит от двух полей, поэтому проверять нужно при изменении любого из них. Частая ошибка — подписаться только на второе поле: человек поправил первый пароль, а красная ошибка у второго осталась.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `<input id="pass"><input id="again"><span id="msg"></span>`;
const pass = document.querySelector("#pass");
const again = document.querySelector("#again");
const msg = document.querySelector("#msg");
functioncheckMatch() {
msg.textContent = pass.value === again.value ? "" : "Пароли не совпадают";
}
pass.addEventListener("input", checkMatch); // оба поля влияют на результат
again.addEventListener("input", checkMatch);
pass.value = "qwerty1"; pass.dispatchEvent(newEvent("input"));
again.value = "qwerty1"; again.dispatchEvent(newEvent("input"));
console.log("[" + msg.textContent + "]"); // [] — совпали
pass.value = "qwerty2"; pass.dispatchEvent(newEvent("input"));
console.log("[" + msg.textContent + "]"); // [Пароли не совпадают] — поправили первое
Типичные ошибки
Ругаемся слишком рано. Красная ошибка после первой буквы раздражает. Используйте «touched».
Проверяем только на change. Тогда человек видит ошибку только после ухода из поля — это уже не «живая» проверка. Основное событие — input.
Забыли проверку при отправке. Человек может нажать «Отправить», вообще не заходя в поле: живая валидация дополняет проверку на submit, а не заменяет её.
Блокируем кнопку без объяснения. Заблокированная кнопка «Отправить» без подсказки, чего не хватает, — плохой интерфейс. Лучше показывать ошибки и оставить кнопку активной.
Внутри обработчика сначала узнайте, скрыт ли пароль: const hidden = pass.type === "password";
Подсказка 2
Тернарный оператор: pass.type = hidden ? "text" : "password"; и по аналогии для eye.textContent.
ЗАДАЧА 7.2
Счётчик символов
средняя
Под полем «О себе» живёт надпись «Осталось: N», где N — сколько символов ещё можно ввести (лимит 40). Она должна быть правильной сразу при загрузке и обновляться на каждое событие input.
Можно редактировать · Ctrl + Enter
document.body.innerHTML = `<textarea id="bio"></textarea><p id="left"></p>`;
const bio = document.querySelector("#bio");
const left = document.querySelector("#left");
const LIMIT = 40;
// 1) функция update() записывает в left текст «Осталось: N»// 2) вызовите её сразу и на событие input
console.log(left.textContent);
bio.value = "Привет!";
bio.dispatchEvent(newEvent("input"));
console.log(left.textContent);
bio.value = "а".repeat(40);
bio.dispatchEvent(newEvent("input"));
console.log(left.textContent);
Ожидаемый результат
Осталось: 40
Осталось: 33
Осталось: 0
Подсказка 1
Длина текста — bio.value.length. Осталось: LIMIT - bio.value.length.
Подсказка 2
Объявите function update() { left.textContent = `Осталось: ${LIMIT - bio.value.length}`; }, вызовите update(); и добавьте bio.addEventListener("input", update);
ЗАДАЧА 7.3
Ругаемся только после ухода из поля
средняя
Реализуйте правило «touched» для поля email. На blur поле считается тронутым и проверяется. На input проверка идёт только если поле уже тронуто. Ошибка — Неверный email, а при корректном адресе текст ошибки пустой.
Нужна переменная let touched = false;. В обработчике blur ставим touched = true и вызываем validate().
Подсказка 2
validate() пишет в error.textContent либо "Неверный email" (если значение не подходит под /^[^\s@]+@[^\s@]+\.[^\s@]+$/), либо пустую строку. Обработчик input: if (touched) validate();
ЗАДАЧА 7.4
Кнопка зависит от двух полей
сложная
Кнопка «Создать аккаунт» должна быть активной только когда пароль не короче 8 символов и повтор пароля ему равен. Напишите updateButton(), вызовите её при загрузке и при вводе в любое из двух полей.
Условие готовности: password.value.length >= 8 && password.value === confirm.value. Кнопка заблокирована, когда оно ложно: submit.disabled = !ready;
Подсказка 2
Подпишитесь обеими строками: password.addEventListener("input", updateButton); и то же для confirm. Не забудьте вызвать updateButton(); один раз сразу.
08 ИИ-блок: правила валидации и регулярки
ИИ-подсказка
Регулярные выражения — одна из лучших задач для ИИ: писать их вручную долго, а объяснить «что хочу» словами легко. Но именно здесь ИИ чаще всего уверенно ошибается. Поэтому схема всегда одна: ИИ предлагает — вы проверяете на своих примерах.
Напомним правила курса: сначала пробуйте сами 15 минут, и никогда не вставляйте код, который не можете объяснить строка за строкой. Сегодня вы уже умеете читать регулярки, так что ответ ИИ можно разобрать по частям.
Контекст: я пишу на чистом JavaScript без библиотек, уровень — новичок. Делаю форму регистрации для курсов в Душанбе.
Задача: составь правила валидации для полей: имя, телефон (Таджикистан, +992), email, пароль, повтор пароля. Для каждого — условие, сообщение об ошибке на русском и регулярное выражение, если оно нужно.
Ограничения: простые регулярки, которые новичок сможет прочитать; без внешних библиотек; сообщения — вежливые, с примером правильного ввода.
Формат ответа: таблица «поле | правило | сообщение об ошибке | регулярка».
Проверка: для каждой регулярки приведи по 3 строки, которые подходят, и 3, которые не подходят, и объясни регулярку по частям.
2. Проверяем регулярку от ИИ на своих кейсах
Не верьте на слово даже самому уверенному ответу. Заведите привычку — таблица тестов:
3–5 подходящих строк, включая «грязные» (с пробелами по краям, в разном регистре).
Пустая строка — должна быть отвергнута (если поле обязательное).
Границы длины: на единицу меньше минимума, ровно минимум, ровно максимум, на единицу больше.
«Мусор» вокруг: abc1234xyz там, где нужны только цифры. Так вы ловите отсутствие ^ и $.
Ваши настоящие данные: телефоны из вашей записной книжки, имена друзей на кириллице, ваш реальный email.
Мини-стенд для проверки регулярки · Ctrl + Enter
const re = /^\+992\d{9}$/; // регулярка от ИИconst cases = [
["+992927771234", true], // подходит
[" +992927771234", false], // пробел в начале — вы решаете, допустимо ли
["+99292777123", false], // на цифру меньше
["+9929277712345", false], // на цифру больше
["abc+992927771234xyz", false], // мусор вокруг
["", false], // пусто
];
for (const [value, expected] of cases) {
console.log((re.test(value) === expected ? "ok " : "FAIL") + " [" + value + "]");
}
3. Что ИИ делает неправильно чаще всего
Что видите в ответе ИИ
В чём проблема
Как исправить
Регулярка без ^ и $
Пропускает мусор вокруг: abc1234xyz проходит как «цифры»
Добавить якоря, если проверяется вся строка
Email на 200 символов с кучей скобок
Никто не сможет прочитать и поддерживать, при этом всё равно не проверяет реального существования адреса
Простая /^[^\s@]+@[^\s@]+\.[^\s@]+$/
\w или [a-z] для имён
Кириллические и таджикские буквы отвергаются
Явные диапазоны [А-Яа-яЁё] и тест на реальных именах
Телефон ^\d{10}$
Шаблон под другую страну
Сказать ИИ в промпте: Таджикистан, +992, 9 цифр
<input type="number"> для телефона
Теряет ведущие нули и «+», у поля есть стрелочки
type="tel" + проверка строкой
Регулярка с флагом g и метод .test()
Объект «помнит» позицию: ответы чередуются true / false
Убрать флаг g для проверок
errorBox.innerHTML = userInput
Опасно: пользователь сможет вставить HTML
errorBox.textContent = ...
«Проверка на клиенте — это защита»
Браузерную проверку легко обойти
Помнить: проверяет ещё и сервер
Ловушка флага g · Ctrl + Enter
const re = /\d/g; // флаг g — «помни, где остановился»
console.log(re.test("1")); // true
console.log(re.test("1")); // false ← ✖ та же строка, другой ответ
console.log(re.test("1")); // true
4. Ещё три полезных промпта
Объясни регулярное выражение /^[a-zA-Z][a-zA-Z0-9_]{2,15}$/ по частям, как для новичка. Для каждой части — что она значит. Затем дай 3 строки, которые подходят, и 3, которые не подходят, с объяснением почему.
Вот моя функция validatePhone: [код]. Придумай 10 тестовых строк, на которых она может сломаться: пробелы, дефисы, буквы, пустая строка, слишком длинный номер. Ответ — массив пар [строка, ожидаемый результат] в виде JS-кода.
Перепиши мои сообщения об ошибках формы так, чтобы они были вежливыми, короткими и называли правило и пример. Не меняй смысл. Было: «Неверно», «Ошибка в пароле», «Плохой телефон».
Главное правило
Прочитайте регулярку от ИИ по частям, как мы делали в разделе 5. Если вы не можете объяснить какую-то часть — попросите ИИ объяснить и проверьте это тестами, либо упростите выражение. Код, который вы не понимаете, вы не сможете исправить, когда он сломается на реальных данных.
Практика с ИИ
Главное в этих задачах — не получить код, а проверить его. Первую задачу выполняйте в чате с ИИ, остальные — здесь.
ЗАДАЧА 8.1
Промпт для правил валидации
лёгкая
Составьте по формуле промпт для формы «Запись в футбольную секцию»: поля имя, возраст (от 7 до 17), телефон +992, email родителя, согласие. Попросите таблицу правил, сообщения об ошибках и регулярки с 3+3 тестовыми строками. Отправьте в ИИ, а полученные регулярки прогоните через мини-стенд из этого раздела. Запишите: какая регулярка не прошла ваши тесты и что вы исправили. Эта задача без автопроверки — отметьте её вручную, когда закончите.
Для возраста регулярка не нужна — это обычное сравнение чисел. Хороший ИИ так и ответит; если он предлагает регулярку для диапазона 7–17, это повод усомниться.
ЗАДАЧА 8.2
Проверка регулярки от ИИ: телефон
средняя
ИИ предложил регулярку для телефона Таджикистана. Запустите стенд и найдите, на каких примерах она ошибается, затем исправьте её, чтобы все строки получили ok и итог был Не прошли: 0. Список проверок не меняйте.
Можно редактировать · Ctrl + Enter
const phoneRe = /^\+992\d{9}/; // регулярка от ИИconst cases = [
["+992927771234", true],
["+992927771234567", false],
["+99292777123", false],
["992927771234", false],
["+992 927771234", false],
["+992abcdefghi", false],
["", false],
];
let failed = 0;
for (const [value, expected] of cases) {
const ok = phoneRe.test(value) === expected;
if (!ok) failed++;
console.log((ok ? "ok" : "FAIL") + ": [" + value + "]");
}
console.log("Не прошли: " + failed);
Какой строке «лишние цифры в конце» удаётся проскочить? Шаблон проверяет начало строки, но ничего не говорит про конец.
Подсказка 2
Добавьте якорь конца строки: /^\+992\d{9}$/
ЗАДАЧА 8.3
Email от ИИ: чиним и доверяем тестам
сложная
Функцию isEmail написал ИИ. Она пропускает лишнее и не обрабатывает пробелы по краям. Исправьте её так, чтобы все восемь проверок прошли: адрес с пробелами по краям считается правильным (пробелы нужно обрезать), а адреса без домена с точкой, без имени, с пробелом внутри или с двумя «@» — неправильными. Список cases не меняйте.
Можно редактировать · Ctrl + Enter
const isEmail = (s) => /^.+@.+$/.test(s); // версия от ИИconst cases = [
["madina@mail.tj", true],
[" madina@mail.tj ", true],
["m.karimova+tj@gmail.com", true],
["madina@", false],
["madina@mail", false],
["@mail.tj", false],
["ma dina@mail.tj", false],
["madina@@mail.tj", false],
];
let failed = 0;
for (const [value, expected] of cases) {
const ok = isEmail(value) === expected;
if (!ok) failed++;
console.log((ok ? "ok" : "FAIL") + ": [" + value + "]");
}
console.log("Не прошли: " + failed);
Точка . в регулярке — «любой символ», в том числе пробел и «@». Для частей адреса нужно «любой символ, кроме пробела и @»: [^\s@]+.
Подсказка 2
(s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s.trim()) — обрезаем пробелы перед проверкой и требуем точку в домене.
09 Практика на уроке: форма регистрации с живой валидацией
Соберём всё вместе в одну форму — такую, какую вы увидите на любом сайте. Мы строим регистрацию на курс: имя, email, пароль и повтор пароля.
Правила нашей формы
Имя — минимум 2 символа без пробелов по краям. Ошибка: «Имя — минимум 2 буквы».
Email — простой формат что-то@что-то.что-то. Ошибка: «Введите email в формате name@mail.tj».
Пароль — минимум 8 символов, затем минимум одна цифра. Ошибки: «Пароль — минимум 8 символов» и «Добавьте хотя бы одну цифру».
Повтор пароля — совпадает с паролем. Ошибка: «Пароли не совпадают».
Ошибки появляются после ухода из поля (blur), дальше обновляются на каждый ввод (input).
При отправке проверяются все поля сразу, курсор встаёт в первое ошибочное, а при успехе выводится «Регистрация: имя, email» (пароль в логи не пишем — никогда!).
План работы
Разметка. Форма с novalidate, у каждого поля — label, name и пустой span.error под ним (data-error="имя поля").
Валидаторы. Функции «значение → текст ошибки», собранные в объект validators (раздел 6).
Живая проверка. Словарь touched, обработчики blur и input (раздел 7).
Отправка. Обработчик submit: «трогаем» все поля, проверяем, фокус на первое ошибочное или успех (раздел 2).
Разметку, стили и общий «каркас» (шаги 1, 3) мы даём готовыми. Ваша работа — два этапа.
Практика: собираем форму
ЗАДАЧА 9.1
Этап 1: валидаторы и живая проверка
средняя
Каркас живой проверки уже работает: showError, validateField и обработчики blur / input написаны. Допишите три валидатора по правилам выше. Сообщения копируйте дословно.
const form = document.querySelector("#reg");
const touched = {};
const validators = {
fullName(value) {
// имя: после trim минимум 2 символа → "Имя — минимум 2 буквы"return"";
},
email(value) {
// формат /^[^\s@]+@[^\s@]+\.[^\s@]+$/ → "Введите email в формате name@mail.tj"return"";
},
password(value) {
// сначала длина: "Пароль — минимум 8 символов", затем цифра: "Добавьте хотя бы одну цифру"return"";
},
};
// ---- готовая часть: показ ошибок и живая проверка ----functionshowError(name, message) {
const input = form.elements[name];
form.querySelector(`[data-error="${name}"]`).textContent = message;
input.classList.toggle("invalid", message !=="");
input.setAttribute("aria-invalid", message ? "true" : "false");
}
functionvalidateField(name) {
const message = validators[name](form.elements[name].value);
showError(name, message);
return message ==="";
}
for (const name of Object.keys(validators)) {
const input = form.elements[name];
input.addEventListener("blur", () => {
touched[name] = true;
validateField(name);
});
input.addEventListener("input", () => {
if (touched[name]) validateField(name);
});
}
// ---- проверка (не менять) ----functiontypeInto(name, value) {
const field = form.elements[name];
field.value = value;
field.dispatchEvent(newEvent("input", { bubbles: true }));
}
functionleave(name) {
form.elements[name].dispatchEvent(newEvent("blur"));
}
functionmsg(name) {
return form.querySelector(`[data-error="${name}"]`).textContent ||"ok";
}
typeInto("fullName", "А");
console.log("имя до blur: " + msg("fullName"));
leave("fullName");
console.log("имя после blur: " + msg("fullName"));
typeInto("fullName", "Али");
console.log("имя после правки: " + msg("fullName"));
typeInto("email", "ali@");
leave("email");
console.log("email: " + msg("email"));
typeInto("email", "ali@mail.tj");
console.log("email после правки: " + msg("email"));
typeInto("password", "abcdefgh");
leave("password");
console.log("пароль 1: " + msg("password"));
typeInto("password", "abc");
console.log("пароль 2: " + msg("password"));
typeInto("password", "abcdefg1");
console.log("пароль 3: " + msg("password"));
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
имя до blur: ok
имя после blur: Имя — минимум 2 буквы
имя после правки: ok
email: Введите email в формате name@mail.tj
email после правки: ok
пароль 1: Добавьте хотя бы одну цифру
пароль 2: Пароль — минимум 8 символов
пароль 3: ok
Пароль — две проверки с ранним return: if (value.length < 8) return "Пароль — минимум 8 символов";, затем if (!/\d/.test(value)) return "Добавьте хотя бы одну цифру";, в конце return "";
ЗАДАЧА 9.2
Этап 2: повтор пароля и отправка
сложная
Три валидатора из этапа 1 уже готовы. Добавьте: (1) валидатор confirm — «Пароли не совпадают»; (2) перепроверку confirm при изменении пароля, если confirm уже «тронут»; (3) обработчик submit: отмена отправки, «трогаем» и проверяем все поля, при ошибке ставим фокус на первое ошибочное поле, иначе выводим Регистрация: имя, email (без пароля).
const form = document.querySelector("#reg");
const touched = {};
const validators = {
fullName(value) {
return value.trim().length < 2 ? "Имя — минимум 2 буквы" : "";
},
email(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()) ? "" : "Введите email в формате name@mail.tj";
},
password(value) {
if (value.length < 8) return"Пароль — минимум 8 символов";
if (!/\d/.test(value)) return"Добавьте хотя бы одну цифру";
return"";
},
// (1) добавьте сюда confirm(value)
};
functionshowError(name, message) {
const input = form.elements[name];
form.querySelector(`[data-error="${name}"]`).textContent = message;
input.classList.toggle("invalid", message !=="");
input.setAttribute("aria-invalid", message ? "true" : "false");
}
functionvalidateField(name) {
const message = validators[name](form.elements[name].value);
showError(name, message);
return message ==="";
}
for (const name of Object.keys(validators)) {
const input = form.elements[name];
input.addEventListener("blur", () => {
touched[name] = true;
validateField(name);
});
input.addEventListener("input", () => {
if (touched[name]) validateField(name);
});
}
// (2) при изменении пароля перепроверьте confirm, если touched.confirm// (3) обработчик submit
form.addEventListener("submit", (event) => {
});
// Песочница блокирует настоящую отправку формы, поэтому клик по кнопке превращаем в submit вручную// (на обычной странице эта вставка не нужна)
form.querySelector("button").addEventListener("click", (event) => {
event.preventDefault();
form.dispatchEvent(newEvent("submit", { cancelable: true }));
});
// ---- проверка (не менять) ----functiontypeInto(name, value) {
const field = form.elements[name];
field.value = value;
field.dispatchEvent(newEvent("input", { bubbles: true }));
}
functionmsg(name) {
return form.querySelector(`[data-error="${name}"]`).textContent ||"ok";
}
typeInto("fullName", "Мадина");
typeInto("email", "madina@mail.tj");
typeInto("password", "qwerty12");
typeInto("confirm", "qwerty1");
form.dispatchEvent(newEvent("submit", { cancelable: true }));
console.log("повтор: " + msg("confirm"));
typeInto("confirm", "qwerty12");
console.log("повтор после правки: " + msg("confirm"));
typeInto("password", "qwerty99");
console.log("после смены пароля: " + msg("confirm"));
typeInto("confirm", "qwerty99");
form.dispatchEvent(newEvent("submit", { cancelable: true }));
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
повтор: Пароли не совпадают
повтор после правки: ok
после смены пароля: Пароли не совпадают
Регистрация: Мадина, madina@mail.tj
Подсказка 1
(1) confirm(value) { return value === form.elements.password.value ? "" : "Пароли не совпадают"; }, (2) form.elements.password.addEventListener("input", () => { if (touched.confirm) validateField("confirm"); });
Подсказка 2
Submit: event.preventDefault(); затем let firstBad = null; и цикл по Object.keys(validators): touched[name] = true;if (!validateField(name) && !firstBad) firstBad = form.elements[name];. Если firstBad есть — firstBad.focus(); return;. Иначе — const data = Object.fromEntries(new FormData(form)); и вывод имени и email.
Эталонное решение (откройте только после своей попытки)
JavaScript
const form = document.querySelector("#reg");
const touched = {};
const validators = {
fullName(value) {
return value.trim().length < 2 ? "Имя — минимум 2 буквы" : "";
},
email(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()) ? "" : "Введите email в формате name@mail.tj";
},
password(value) {
if (value.length < 8) return"Пароль — минимум 8 символов";
if (!/\d/.test(value)) return"Добавьте хотя бы одну цифру";
return"";
},
confirm(value) {
return value === form.elements.password.value ? "" : "Пароли не совпадают";
},
};
functionshowError(name, message) {
const input = form.elements[name];
form.querySelector(`[data-error="${name}"]`).textContent = message;
input.classList.toggle("invalid", message !=="");
input.setAttribute("aria-invalid", message ? "true" : "false");
}
functionvalidateField(name) {
const message = validators[name](form.elements[name].value);
showError(name, message);
return message ==="";
}
for (const name of Object.keys(validators)) {
const input = form.elements[name];
input.addEventListener("blur", () => {
touched[name] = true;
validateField(name);
});
input.addEventListener("input", () => {
if (touched[name]) validateField(name);
});
}
form.elements.password.addEventListener("input", () => {
if (touched.confirm) validateField("confirm");
});
form.addEventListener("submit", (event) => {
event.preventDefault();
let firstBad = null;
for (const name of Object.keys(validators)) {
touched[name] = true;
if (!validateField(name) && !firstBad) firstBad = form.elements[name];
}
if (firstBad) {
firstBad.focus();
return;
}
const data = Object.fromEntries(newFormData(form));
console.log(`Регистрация: ${data.fullName}, ${data.email}`);
});
// Песочница блокирует настоящую отправку формы, поэтому клик по кнопке превращаем в submit вручную// (на обычной странице эта вставка не нужна)
form.querySelector("button").addEventListener("click", (event) => {
event.preventDefault();
form.dispatchEvent(newEvent("submit", { cancelable: true }));
});
ИИ-проверка вашей формы
Когда ваш код работает, попросите ИИ придумать для него тесты: «Вот моя форма регистрации и валидаторы: [код]. Придумай 10 входных значений для каждого поля, на которых проверка может ошибиться (пробелы, пустое, слишком длинное, спецсимволы). Для каждого — ожидаемый результат». Прогоните эти значения через свои валидаторы и найдите расхождения.
★ Домашнее задание: форма из 6 полей
Сделайте форму регистрации на курс «Frontend + AI» с шестью полями, полной валидацией и понятными текстами ошибок. Начинайте с классной работы: она уже содержит каркас, вам нужно расширить его и довести до аккуратного результата.
Поля формы
#
Поле
Правило
Пример текста ошибки
1
Имя и фамилия
Минимум 2 слова, каждое от 2 букв, только буквы, пробелы и дефис
«Введите имя и фамилию, например: Мадина Каримова»
2
Телефон
Формат +992 и 9 цифр; пробелы и дефисы при вводе допускаются
«Номер в формате +992 92 777 12 34»
3
Email
Простой формат с «@» и точкой в домене
«Введите email, например madina@mail.tj»
4
Пароль
От 8 символов, есть цифра, есть заглавная буква, нет пробелов
Покажите все нарушенные правила списком или через « · »
5
Повтор пароля
Совпадает с паролем (перепроверяется при смене пароля)
«Пароли не совпадают»
6
Согласие с правилами курса
Чекбокс должен быть отмечен
«Без согласия записаться нельзя»
Что должно работать
☐ Разметка: form novalidate, у каждого поля есть label, name и правильный type (tel, email, password, checkbox).
☐ Каждый валидатор — отдельная функция «значение → текст ошибки или пустая строка».
☐ Живая валидация по схеме «touched»: ошибка появляется после ухода из поля и пропадает сразу при исправлении.
☐ При отправке проверяются все поля, фокус ставится на первое ошибочное.
☐ Ошибки показываются под полями (textContent), у поля есть красная рамка и aria-invalid.
☐ При успехе: на странице появляется сообщение «Спасибо, Имя! Мы свяжемся с вами по номеру ...», данные собраны через FormData, форма сбрасывается. Пароль нигде не показывается и не выводится в консоль.
☐ Бонус: кнопка «Показать пароль» и счётчик оставшихся символов — любой один из них.
Что сдать
Ссылка на репозиторий GitHub (папка lesson-10 с файлами index.html, style.css, script.js) или архив в Telegram-группу.
Три скриншота: пустая отправка (все ошибки), частично заполненная форма, успешная отправка.
Файл tests.md: таблица минимум из 10 тестовых значений для телефона и email — «значение → ожидаемый результат» — и отметка, какие ваш код прошёл.
Если вы использовали ИИ: в README три строки — что именно просили, что взяли, что исправили руками. На проверке преподаватель может попросить объяснить любую строку.
Как оцениваем (0–10)
Критерий
Баллы
Разметка: все 6 полей, label, name, type, novalidate
1
Валидаторы имени, телефона и email работают на плохих и хороших данных
3
Пароль (все правила) и повтор пароля (в том числе при смене пароля)
2
Чекбокс согласия
1
Живая валидация (touched), без «ругани» с первой буквы
1
Отправка: все ошибки сразу, фокус на первом, успех, сброс формы
1
Понятные тексты ошибок (называют правило и пример), чистый код
1
Подсказка по времени
Закладывайте 2–3 часа. Сначала сделайте только валидаторы и проверьте их в консоли на 5–10 значениях, потом подключайте страницу. Если застряли — сначала 15 минут сами, потом вопрос в чат группы (с кодом и текстом ошибки), потом ИИ по формуле промпта.
✓ Шпаргалка урока
Читаем поля:form.elements.name.value для текста, числа, списка и группы радио; .checked для чекбокса. value — всегда строка, число — через Number(...) или valueAsNumber.
submit: слушаем на форме, первой строкой event.preventDefault(), значения читаем внутри обработчика. form.reset() — очистить, form.dispatchEvent(new Event("submit", { cancelable: true })) — отправить программно.
FormData:new FormData(form), .get / .getAll / .has, Object.fromEntries(data). Без name, с disabled и неотмеченные чекбоксы в данные не попадают.
Встроенная валидация:required, type, min/max, pattern; checkValidity(), validity.*, setCustomValidity(). Свои тексты и сложные правила — своим JS и novalidate.
Регулярки:/шаблон/.test(строка). \d цифра, \s пробел, [a-z] диапазон, + * ? {n,m} количество, ^ $ якоря начала и конца. Телефон: /^\+992\d{9}$/. Всегда тестируйте на плохих данных.
Свой валидатор — функция «значение → текст ошибки или ""». Все поля — в объекте validators, ошибки показываем через textContent под полем, плюс aria-invalid. Тексты ошибок называют правило и пример.
Живая проверка:blur делает поле «тронутым», после этого input перепроверяет на каждый ввод. Сравнение двух полей — слушаем оба. Проверка при submit остаётся обязательной.
ИИ: просим правила валидации по формуле промпта и всегда проверяем регулярки на своих кейсах: подходящие, пустая строка, границы, мусор вокруг, реальные данные. Не вставляем код, который не можем объяснить. Проверка в браузере — удобство, защита — на сервере.