WEB. Справочная информация
JavaScript и jQuery
AJAX
jQuery(..).load
-
url запрашиваемой страницы
-
передаваемые данные (необязательный параметр)
-
функция которой будет скормлен результат (необязательный параметр)
// по окончанию загрузки страницы
$(document).ready(function(){
// вешаем на клик по элементу с id = example-1
$('#example-1').click(function(){
// загрузку HTML кода из файла example.html
$(this).load('ajax/example.html');
})
});
Example<br/>
Data Loaded By AJAX<br/>
Bye-Bye
jQuery.ajax
-
async — асинхронность запроса, по умолчанию true
-
cache — вкл/выкл кэширование данных браузером, по умолчанию true
-
contentType — по умолчанию «application/x-www-form-urlencoded»
-
data— передаваемые данные — строка иль объект
-
dataFilter — фильтр для входных данных
-
dataType— тип данных возвращаемых в callback функцию (xml, html, script, json, text, _default)
-
global — тригер — отвечает за использование глобальных AJAX Event'ов, по умолчанию true
-
ifModified — тригер — проверяет были ли изменения в ответе сервера, дабы не слать еще запрос, по умолчанию false
-
jsonp — переустановить имя callback функции для работы с JSONP (по умолчанию генерируется на лету)
-
processData — по умолчанию отправляемые данный заворачиваются в объект, и отправляются как «application/x-www-form-urlencoded», если надо иначе — отключаем
-
scriptCharset — кодировочка — актуально для JSONP и подгрузки JavaScript'ов
-
timeout — время таймаут в миллисекундах
-
type— GET либо POST
-
url— url запрашиваемой страницы
-
beforeSend — срабатывает перед отправкой запроса
-
error — если произошла ошибка
-
success — если ошибок не возникло
-
complete — срабатывает по окончанию запроса
-
username — логин
-
password — пароль
$.ajax({
url: '/ajax/example.html', // указываем URL и
dataType : "json", // тип загружаемых данных
success: function (data, textStatus) { // вешаем свой обработчик на функцию success
$.each(data, function(i, val) { // обрабатываем полученные данные
/* ... */
});
}
});
jQuery.get
-
url запрашиваемой страницы
-
передаваемые данные (необязательный параметр)
-
callback функция, которой будет скормлен результат (необязательный параметр)
-
тип данных возвращаемых в callback функцию (xml, html, script, json, text, _default)
jQuery.post
-
url запрашиваемой страницы
-
передаваемые данные (необязательный параметр)
-
callback функция, которой будет скормлен результат (необязательный параметр)
-
тип данных возвращаемых в callback функцию (xml, html, script, json, text, _default)
$(document).ready(function(){ // по завершению загрузки страницы
$('#example-3').click(function(){ // вешаем на клик по элементу с id = example-3
$.post('ajax/example.xml', {}, function(xml){ // загрузку XML из файла example.xml
$('#example-3').html('');
$(xml).find('note').each(function(){ // заполняем DOM элемент данными из XML
$('#example-3').append('To: ' + $(this).find('to').text() + '<br/>')
.append('From: ' + $(this).find('from').text() + '<br/>')
.append('<b>' + $(this).find('heading').text() + '</b><br/>')
.append( $(this).find('body').text() + '<br/>');
});
}, 'xml'); // указываем явно тип данных
})
});
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
jQuery.getJSON
-
url запрашиваемой страницы
-
передаваемые данные (необязательный параметр)
-
callback функция, которой будет скормлен результат (необязательный параметр)
$(document).ready(function(){ // по завершению загрузки страницы
$('#example-4').click(function(){ // вешаем на клик по элементу с id = example-4
$.getJSON('ajax/example.json', {}, function(json){ // загрузку JSON данных из файла example.json
$('#example-4').html('');
// заполняем DOM элемент данными из JSON объекта
$('#example-4').append('To: ' + json.note.to + '<br/>')
.append('From: ' + json.note.from + '<br/>')
.append('<b>' + json.note.heading + '</b><br/>')
.append( json.note.body + '<br/>');
});
})
});
{
note:{
to:'Tove',
from:'Jani',
heading:'Reminder',
body:'Don\'t forget me this weekend!'
}
}
jQuery.getScript
-
url запрашиваемого скрипта
-
callback функция, которой будет скормлен результат (необязательный параметр)
JavaScript:
$(document).ready(function(){ // по завершению загрузки страницы
$('#example-5').click(function(){ // вешаем на клик по элементу с id = example-5
$.getScript('ajax/example.js', function(){ // загрузку JavaScript'а из файла example.js
testAjax(); // выполняем загруженный JavaScript
});
})
});
function testAjax() {
$('#example-5').html('Test completed'); // изменяем элемент с id = example-5
}
Отправка Формы
Примеры использования JSONP

http://api.domain.com/?type=jsonp&query=test&callback=?
События
$("#loading").bind("ajaxSend", function(){
$(this).show(); // показываем элемент
}).bind("ajaxComplete", function(){
$(this).hide(); // скрываем элемент
});
$.ajax({
beforeSend: function(){
// Handle the beforeSend event
},
complete: function(){
// Handle the complete event
}
// ...
});

-
ajaxStart— Данный метод вызывается в случае когда побежал AJAX запрос, и при этом других запросов нету
-
beforeSend— Срабатывает до отправки запроса, позволяет редактировать XMLHttpRequest. Локальное событие
-
ajaxSend— Срабатывает до отправки запроса, аналогично beforeSend
-
success— Срабатывает по возвращению ответа, когда нет ошибок ни сервера, ни вернувшихся данных. Локальное событие
-
ajaxSuccess— Срабатывает по возвращению ответа, аналогично success
-
error— Срабатывает в случае ошибки. Локальное событие
-
ajaxError— Срабатывает в случае ошибки
-
complete— Срабатывает по завершению текущего AJAX запроса (с ошибкои или без — срабатывает всегда).Локальное событие
-
ajaxComplete— Глобальное событие, аналогичное complete
-
ajaxStop— Данный метод вызывается в случае когда больше нету активных запросов
Javascript - appendChild(), removeChild()
<div id="raz10"><input type="button" value="Нажать"></div>
<script>
var div = document.querySelector('#raz10');
input = div.childNodes;
input[0].onclick = function() {
alert('Не работает');
}
div.innerHTML += '<input type="button" value="Я вновь созданный">';
</script>
<div id="raz10"><input type="button" value="Нажать"></div>
<script>
var div = document.querySelector('#raz10');
input = div.childNodes;
div.innerHTML += '<input type="button" value="Я вновь созданный">';
input[0].onclick = function() {
alert('Работает');
}
</script>
Как удалить элемент на JavaScript
<button onclick="this.parentNode.removeChild(this);">Вариант 1</button>
<button onclick="this.remove();">Вариант 2</button>
<button onclick="this.outerHTML='';">Вариант 3</button>
Как добавить тег HTML
<style>
#raz div {
transition: 3s;
opacity: 0;
}
</style>
<div id="raz">
<input type="button" value="Добавить">
</div>
appendChild() в конец
<script>
document.querySelector('#raz input').onclick = function() {
var theDiv = document.createElement('div'); // создать новый тег div
theDiv.innerHTML = 'HTML-содержимое'; // его содержимое
this.parentNode.appendChild(theDiv); // поместить новый тег последним в div с id="raz" (вместо this.parentNode может быть любой другой элемент DOM)
getComputedStyle(theDiv).opacity;
theDiv.style.opacity = '1';
}
</script>
insertBefore() в начало
<script>
document.querySelector('#raz input').onclick = function() {
var theDiv = document.createElement('div');
theDiv.innerHTML = 'HTML-содержимое';
this.parentNode.insertBefore(theDiv, this.parentNode.firstChild); // поместить новый тег первым в div с id="raz"
getComputedStyle(theDiv).opacity;
theDiv.style.opacity = '1';
}
</script>
insertBefore() после конкретного элемента
<script>
document.querySelector('#raz input').onclick = function() {
var theDiv = document.createElement('div');
theDiv.innerHTML = 'HTML-содержимое';
this.parentNode.insertBefore(theDiv, this.nextSibling);
getComputedStyle(theDiv).opacity;
theDiv.style.opacity = '1';
}
</script>
insertBefore() до указанного элемента
<script>
document.querySelector('#raz input').onclick = function() {
var theDiv = document.createElement('div');
theDiv.innerHTML = 'HTML-содержимое';
this.parentNode.insertBefore(theDiv, this.previousSibling);
getComputedStyle(theDiv).opacity;
theDiv.style.opacity = '1';
}
</script>
innerHTML внутри элемента (взять содержимое в новый тег)
<style>
#raz8 span {
display: inline-block;
padding: 3px;
border: 1px solid;
}
</style>
<button type="button" id="raz8">новый тег внутри</button>
<script>
(function(){
var i = 1;
document.querySelector('#raz8').onclick = function() {
this.innerHTML = '<span style="border-color: rgba(255,0,0,'+i+');">'+this.innerHTML+'</span>';
i = i - 0.1;
}
})()
</script>
-
создать нового потомка,
-
переместить во вновь созданного потомка всех остальных потомков.
replaceChild(newChild, oldChild) снаружи элемента (окружить новым тегом)
<style>
.raz9 {
display: inline-block;
padding: 3px;
border: 1px solid rgba(255,0,0,0);
transition: 3s;
}
</style>
<button type="button" id="raz9">новый тег снаружи</button>
<script>
document.querySelector('#raz9').onclick = function() {
var theSpan = document.createElement('span');
theSpan.setAttribute('class', 'raz9');
this.parentNode.replaceChild(theSpan, this); // заменить текущий элемент на созданный
theSpan.appendChild(this); // содержимое созданного элемента будет текущий элемент
getComputedStyle(theSpan).borderTopColor;
theSpan.style.borderColor = 'rgba(255,0,0,1)';
}
</script>
Как добавить текст
<div id="raz7">
<input type="button" value="Добавить текст">
</div>
<script>
(function(){
var i = 1;
document.querySelector('#raz7 input').onclick = function() {
var theText = document.createTextNode(' текст'+i);
this.parentNode.appendChild(theText);
i++;
}
})()
</script>
Как переместить элемент
<style>
#raz5, #raz6 {
height: 50px;
padding: 5px;
}
#raz5 {
background: green;
}
#raz6 {
background: blue;
}
</style>
<div id="raz5">
<input type="button" value="Перенести в синий прямоугольник">
</div>
<div id="raz6"></div>
<script>
document.querySelector('#raz5 input').onclick = function() {
document.querySelector('#raz6').appendChild(this);
}
</script>
Как скопировать (клонировать) элемент
Сделать дубликат кода с помощью cloneNode(true|false)
<button type="button" class="raz1">1</button><button type="button" class="raz1">2</button>
<script>
Array.prototype.slice.call(document.querySelectorAll('.raz1')).forEach(function(a, index, all) {
a.addEventListener('click', function() {
var theI = a.cloneNode(true);
a.parentNode.insertBefore(theI, a.nextSibling);
alert(index+1+' из '+all.length);
}, false);
})
</script>
Как запретить повторный onclick
<input type="button" value="Добавить после input" id="dobavit">
<script>
document.querySelector('#dobavit').onclick = function() {
theDiv = document.createElement('div');
theDiv.innerHTML = 'содержащий текст';
this.parentNode.insertBefore(theDiv, this.nextSibling);
this.onclick = null;
}
</script>
Как создать элемент, а потом его же удалить
<input type="button" value="Новый CSS" onclick="include('on')">
<input type="button" value="Старый CSS" onclick="include('off')">
<script>
var include = (function(){
return function (status){
if(status == 'on'){ // если include('on')
theStyle = document.createElement('style'); // создать новый тег style
theStyle.innerHTML = 'body {background: DarkSalmon;}';
document.querySelector('head').appendChild(theStyle); // тег style поместить в конец head
}else{
theStyle.parentNode.removeChild(theStyle); // удалить созданный тег style
}
}
})();
</script>
В чём отличие innerHTML от appendChild(), removeChild()
<input type="button" value="Новый CSS" onclick="include1('on')">
<input type="button" value="Попытка вернуть старый CSS" onclick="include1('off')">
<input type="button" value="Перезагрузить страницу" onclick="location.reload();">
<script>
var include1 = (function(){
return function (status){
if(status == 'on'){ // если include1('on')
document.querySelector('head').innerHTML = '<style>body {background: DarkSalmon;}</style>'; // сменить содержимое тега head на прописанное в кавычках
}else{ // иначе
document.querySelector('head').innerHTML = '';
}
}
})();
</script>
<input type="button" value="Новый CSS" onclick="include2('on')">
<input type="button" value="Старый CSS" onclick="include2('off')">
<script>
var include2 = (function(){
return function (status){
if(status == 'on'){
document.querySelector('head').innerHTML = document.querySelector('head').innerHTML.replace(/$/g, '<style class="newStyle">body {background: DarkSalmon;}</style>');
}else{
document.querySelector('head').innerHTML = document.querySelector('head').innerHTML.replace(/<style class="newStyle">[\s\S]*?<\/style>/g, '');
}
}
})();
</script>
<style class="nStyle"></style>
<input type="button" value="Новый CSS" onclick="include3('on')">
<input type="button" value="Старый CSS" onclick="include3('off')">
<script>
var include3 = (function(){
return function (status){
if(status == 'on'){
document.querySelector('style.nStyle').innerHTML = 'body {background: DarkSalmon;}';
}else{
document.querySelector('style.nStyle').innerHTML = '';
}
}
})();
</script>
JavaScript: Советы и рекомендации
Оператор !!
console.log(!! 0) // output: false
console.log(!! 1) // output: true
console.log(Boolean(1)) // output: true
console.log(Boolean(0)) // output: false
Конвертировать string → number
const string = '101'
console.log(+string) // output: 101
console.log(Number(string)) // output: 101
Обратный метод reverse()
const numbers = ['1', '2', '3']
console.log(numbers.reverse()) // output: [ "3", "2", "1" ]
Математические функции Math.min & max
const numbers = [1, 2 ,3, 4, 5]
console.log(Math.min(...numbers)) // output: 1
console.log(Math.max(...numbers)) // output: 5
Объединить массивы
const fruits = ['🍎', '🍌']
const vegetables = ['🥔', '🥕']
const food = [...fruits, ...vegetables]
console.log(food) // output: [ "🍎", "🍌" , "🥔", "🥕" ]
Ложные значения
undefined , null , NaN , 0 , 0n (BigInt 0), -0 ""(empty string),false,document.all
Тернарный оператор
let number = 1
if (number == 1) {
console.log('number is one')
} else {
console.log('number is not one')
}
// Syntax: condition ? exprIfTrue : exprIfFalse (MDN)
console.log(number === 1 ? "number is one" : "number is not one");
Удалить дубли в массиве
const fruits = ['🍎', '🍊', '🍎', '🍊']
// Method 1:
const filteredFruits = Array.from(new Set(fruits))
console.log(filteredFruits) // output: Array [ "🍎", "🍊" ]
// Method 2:
const filteredFruits = [...new Set(fruits)]
console.log(filteredFruits) // output: Array [ "🍎", "🍊" ]
Метод map()
const numbers = [1, 2, 3, 4, 5]
const mapedNumbers = numbers.map(element => element + 1)
console.log(mapedNumbers) // output: [2, 3, 4, 5, 6]
Метод includes()
const hearts = ['🧡', '💙', '🤍']
console.log(hearts.includes('🧡')) // output: true
console.log(hearts.includes('❤️')) // output: false
Метод filter()
const numbers = [1, 5, 6, 7, 4]
const filteredArray = numbers.filter(element => element > 4)
console.log(filteredArray) // output: [ 5, 6, 7 ]
Кнопка прокрутки вверх
const button = document.querySelector('button')
button.addEventListener('click', function () {
window.scrollTo(0,0)
})

outerWidth
outerWidth( [margin] )
|
margin (необязательно)
|
Логическое значение
|
|
|---|---|---|
|
Если указано значение true, то значение margin берется во внимание при расчете высоты.
|
||
var p = $("p:first");
$("p:last").text( "outerWidth:" + p.outerWidth()+ " , outerWidth(true):" + p.outerWidth(true) );
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$(document).ready(function(){
var p = $("p:first");
$("p:last").text( "outerWidth:" + p.outerWidth()+ " , outerWidth(true):" + p.outerWidth(true) );
});
</script>
<style>
p { margin:10px;padding:5px;border:2px solid #666; }
</style>
</head>
<body>
<p>Hello</p><p></p>
</body>
</html>
window.history
History API даёт доступ к управлению историей браузера в рамках текущей сессии. Браузер создаёт новую сессию, когда пользователь открывает новую вкладку или новое окно браузера.
С помощью History API можно переходить по истории вперёд, назад и управлять содержимым истории. Доступ к API осуществляется с помощью объекта window.history.
Основные методы:
- back() перемещает пользователя по истории на страницу назад;
- forward() перемещает пользователя по истории на страницу вперёд;
- go() универсальный метод для перемещения по истории вперёд или назад;
- pushState() добавляет новую запись в истории сессии;
- replaceState() изменяет текущую запись в истории сессии.
Как пишется
Сгруппируем методы и свойства History API на две части:
- используемые для перемещения по истории в текущей сессии.
- используемые для управления историей.
Перемещение по истории браузера
Метод back() перемещает пользователя по истории назад. Это работает аналогично нажатию кнопки «Назад» в браузере.
Метод back() не принимает аргументов и не возвращает результата.
window.history.back()
Чтобы переместиться по истории вперёд, используется метод forward(). Он работает аналогично кнопке «Вперёд» в браузере.
Метод forward() не принимает аргументов и не возвращает результата.
window.history.forward()
Метод go() является универсальным и позволяет переместиться по истории вперёд или назад относительно текущей страницы.
Метод принимает один аргумент — число, которое определяет, на сколько шагов по истории вперёд или назад нужно перейти. Отрицательное значение определяет сколько шагов назад, а положительные — вперёд.
window.history.go(2)
// переместит на две позиции вперёд по истории
window.history.go(-1)
// переместит назад на одну страницу
Нулевая позиция 0 означает текущую страницу и вызов с нулём обновляет текущую страницу.
window.history.go(0)
Метод back() аналогичен вызову window.history.go(-1), а метод forward() — вызову window.history.go(1).
Все методы для перемещения по истории браузера back(), forward() и go() являются асинхронными.
О том для чего вообще может понадобиться передавать данные во время навигации смотрите раздел «На практике».
Свойство length хранит количество записей в истории браузера в текущей сессии, включая текущую страницу. То есть новая история всегда будет начинаться с 1.
length работает только для чтения, при изменении значения ничего не произойдёт.
Управление историей
Для создания новой записи в истории используется метод pushState(), а для модификации текущей записи — replaceState().
Оба метода похожи с точки зрения использования и оба принимают три аргумента:
- объект состояния, в который можно добавить любые данные, необходимые для навигации;
- неиспользуемый параметр, который существует по историческим причинам;
- новый URL-адрес, этот параметр опциональный.
У передаваемых аргументов есть ограничения, о которых стоит помнить:
- В первый аргумент-объект можно записать любой объект с любыми данными, главное чтобы объект был сериализуемым. Браузеры могут накладывать ограничения на размер такого объекта.
- Второй аргумент игнорируется всеми браузерами кроме Safari. Во избежание ошибок рекомендуют передавать пустую строку.
- Третий аргумент, URL-адрес, необязательный. Если этот аргумент не был задан, то будет использован текущий URL. Новый URL-адрес должен использовать тот же протокол, домен и порт, иначе будет выброшена ошибка. Если новая запись ведёт на новый относительный адрес, то можно не передавать адрес полностью вместе с доменом, а записать только относительную часть через слэш. Например, /profile.
О том для чего вообще может понадобиться передавать данные во время навигации смотрите раздел «На практике».
Рассмотрим работу window.history на примере. В этом примере у нас получится замкнутый круг переходов по страницам. Вы можете поиграться с примером, ниже я разберу код, который используется в демке.
Предположим, что мы зашли на главную страницу сайта с новой вкладки. С помощью кнопки добавим новую запись в историю:
window.history.pushState(
{},
'',
'about.html'
)
Адрес в браузерной сроке location.path изменился, а так же обновилось количество записей в истории history.length. Теперь если нажать кнопку перезагрузки страницы, то мы увидим новую страницу.
Теперь можем изменить текущую запись в истории, нажав на кнопку. Ниже приведён фрагмент код, который делает изменение истории:
window.history.replaceState(
{},
'',
'index.html'
)
Адрес в браузерной строке снова изменился, но количество записей осталось прежним, так как мы ничего не добавили. Далее продолжаем путь на следующую страницу.
Страница цен является последним пунктом в примере, с него можно только вернуться по истории назад на предыдущую страницу.
window.history.back()
// Или можно сделать window.history.go(-1)
Помните, как на предыдущей странице видели страницу «О нас» по ссылке about.html? Мы же попали на главную страницу. Это произошло потому что мы изменили текущую запись в истории, изменив её URL-адрес. В результате, когда мы возвращаемся назад по истории, то используем изменённый URL.
Как понять
Классическая навигация
Основным способом в вебе навигации являются ссылки <a>. С помощью ссылок страницы соединяются друг с другом.
Особенность этого способа навигации в том, что при переходе на новый адрес страница перезагружается. Каждый такой переход сохраняется в истории браузера. История может выглядеть так:
https://example.com/buy
https://example.com/prices
https://example.com/about
https://example.com/
Верхние пункты списка — это недавно посещённые страницы, а последний пункт — страница, с которой началась сессия.
Навигация в одностраничных приложениях
С другой стороны, есть одностраничные приложения, которые работают без перезагрузки страницы. Все содержимое они отрисовывают с помощью JavaScript. Приложение может перерисовать всю страницу, но на истории браузера это никак не отобразится.
История в браузере будет выглядеть как единственный пункт:
https://example.com/
Это ведёт к плохому пользовательскому опыту, потому что пользователю сложно понять по каким страницам он перемещался.
Отсутствие истории ломает нативное поведение браузерных кнопок вперёд и назад, что очень важно, так как эти кнопки часто привязаны к жестам или системным кнопкам в смартфонах.
Сначала эту проблему решали добавлением хэша в адрес сайта с помощью поля window.location.hash. Присвоив новое значение в location.hash, адресная строка сразу же обновлялась и в историю добавлялась новая запись. Но с помощью хэша нельзя строить читаемые многоуровневые урлы, например https://example.com/category/cars/item/12345.
С появлением History API появилась возможность напрямую добавлять записи в историю просмотров. History API так же расширяет возможности для программного перемещения по истории браузера. Это позволяет создавать полноценную навигацию в одностраничных приложениях, менять адрес в браузерной строке и все это будет происходить без перезагрузки страницы.
Работа плагина jQuery validate изнутри
Как валидировать форму
1. Использовать имена классов как правила
<form action="/" method="post">
<input id="Name" type="text" name="Name" value="" class ="required" />
<input type="submit" value="Submit" />
</form>
Использование метода «addClassRules»
$.validator.addClassRules({
name: {
required: true,
minlength: 2
},
zip: {
required: true,
digits: true,
minlength: 5,
maxlength: 5
}
});
<input class="zip" type="text" name="zipCode" />
$.validator.addMethod("newrequired", $.validator.methods.required, "new name is required");
2. Добавление правил как JSON объекта к методу validate()
<form>
<input id="something" type="text" name="userEmail" />
<input id="submit" type="submit" value="submit" />
</form>
$('form').validate({
rules: {
userEmail: {
email: true,
required: true
}
}
});
- Этот подход дает нам возможность использовать больше правил валидации, которые принимают аргументы, такие как minlength, remote, equalTo и т.д.
- Отличный и настраиваемый вручную контроль над всем.
- Но пользователь должен делать отдельную функцию «validate» с разными опциями для каждой формы.
Добавление или удаление динамических правил.
Добавление правил
$('.input').rules('add', {
required: true,
messages: {
required: true
}
})
Удаление правил
$('.input').rules('remove', 'min max');
Подход настройки вручную
var validator = $('form').data('validator');
validator.settings.rules.objectName = {
required: true
}
$.extend(validator.settings, { rules: rules, messages: messages });
Сообщения валидации и как они работают
$('form').validate({
rules: {
userEmail: {
email: true,
required: true
}
},
messages: {
userEmail: {
email: "Please enter your email",
required: "*"
}
}
});
<input id="Email" title="you have to enter a value" type="text" name="Email" />
Добавление собственных правил валидации
$.validator.addMethod
-
имя правила;
-
функцию, которая осуществляет валидацию;
-
сообщение по умолчанию.
$('form').validate({
rules: {
firstname: {
compare: {
type: "notequal",
otherprop: "lastname"
}
}
}
});
$.validator.addMethod("notnumbers", function(value, element) {
return !/[0-9]*/.test(value);
},
"Please don't insert numbers.")
Что именно происходит, когда мы вызываем метод «validate»
//getting the validator
var validator = $("selector").data("validator")
var validator = $(".selector").validate(/* rules will be omitted */)
Присоединение событий формы
$(".selector").validate({
onfocusout: false,
onkeyup: false,
onclick: false,
onsubmit: false
});
Регулярные выражения для валидации распространенных видов данных
-
Номер кредитки:[0-9]{13,16}
-
ICQ:([1-9])+(?:-?\d){4,}
-
Набор из букв и цифр (латиница):^[a-zA-Z0-9]+$
-
Набор из букв и цифр (латиница + кириллица):^[а-яА-ЯёЁa-zA-Z0-9]+$
-
Домен (например abcd.com):^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$
-
IPv4:((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)
-
IPv6:((^|:)([0-9a-fA-F]{0,4})){1,8}$
-
Имя пользователя (с ограничением 2-20 символов, которыми могут быть буквы и цифры, первый символ обязательно буква):^[a-zA-Z][a-zA-Z0-9-_\.]{1,20}$
-
Пароль (Строчные и прописные латинские буквы, цифры):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$
-
Пароль (Строчные и прописные латинские буквы, цифры, спецсимволы. Минимум 8 символов):(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$
-
Дата в формате YYYY-MM-DD:[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])Более строгая проверка:(19|20)\d\d-((0[1-9]|1[012])-(0[1-9]|[12]\d)|(0[13-9]|1[012])-30|(0[13578]|1[02])-31)
-
Дата в формате DD/MM/YYYY:(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d
-
Целые числа и числа с плавающей точкой (разделитель точка):\-?\d+(\.\d{0,})?
-
UUID:^[0-9A-Fa-f]{8}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{12}$
-
Широта или долгота:-?\d{1,3}\.\d+
-
E-mail:^[-\w.]+@([A-z0-9][-A-z0-9]+\.)+[A-z]{2,4}$
-
URL на латинице. Если нужно распознавать и кириллические домены, необходимо изменить все «a-z0-9» на «а-яёa-z0-9» и добавить в список доменных зон «рф»:~^(?:(?:https?|ftp|telnet)://(?:[a-z0-9_-]{1,32}(?::[a-z0-9_-]{1,32})?@)?)?(?:(?:[a-z0-9-]{1,128}\.)+(?:ru|su|com|net|org|mil|edu|arpa|gov|biz|info|aero|inc|name|[a-z]{2})|(?!0)(?:(?!0[^.]|255)[0-9]{1,3}\.){3}(?!0|255)[0-9]{1,3})(?:/[a-z0-9.,_@%&?+=\~/-]*)?(?:#[^ '\"&]*)?$~i
-
Время в формате HH:MM:SS:^([0-1]\d|2[0-3])(:[0-5]\d){2}$
-
Mac-адрес:([0-9a-fA-F]{2}([:-]|$)){6}$|([0-9a-fA-F]{4}([.]|$)){3}
События и их обработка в jQuery
Обработка событий с помощью методов jQuery

// функция on
.on(events,handler);
// функция one
.one(events,handler);
// events - событие или список событий через пробел, при наступлении которых необходимо выполнить обработчик (handler)
// handler - функция (обработчик события)
// краткая запись функции on
.event(handler);
// event - название события (можно использовать для обработки только тех событий, для которых в jQuery создана такая краткая запись)
// использование в качестве обработчика анонимной функции
$('.btn').on('click', function() {
// действия, которые будут выполнены при наступлении события...
console.log($(this).text());
});
// использование обычной функции в качестве обработчика
var myFunction = function() {
console.log($(this).text());
}
$('.btn').on('click', myFunction);
$('.btn').click(function() {
// действия, которые будут выполнены при наступлении события...
console.log($(this).text());
});
Дополнительная информация о событии
$('#demo').on('click', function(e){
// e – объект Event, содержащий дополнительную информацию о произошедшем событии
// часто используемые свойства объекта Event
e.preventDefault(); //отменить выполнение действия по умолчанию
e.stopPropagation(); //остановить дальнейшее всплытие события
// e.type – получить тип события
// e.target – ссылка на элемент, на котором произошло событие
// e.currentTarget - ссылка на текущий элемент (для которого сработал обработчик). Это свойство, как правило, равно функции this.
// e.currentTarget === this
// e.which – код клавиши (для мыши), код кнопки или символа (для клавиатуры)
//e.pageX, e.pageY – координаты курсора, относительно левого верхнего угла документа
});
Пространство имён
// событие click в пространстве имён first
$('#demo').on('click.first',function(){
console.log('1 обработчик события click');
});
// событие click в пространстве имён second
$('#demo').on('click.second',function(){
console.log('2 обработчик события click');
});
// вызвать событие click в пространстве имён first
$('#demo').trigger('click.first');
// вызвать событие click в пространстве имён second
$('#demo').trigger('click.second');
//удалить обработчики события click в пространстве имён first
$('#demo').off('click.first');
//удалить обработчики события click в пространстве имён second
$('#demo').off('click.second');
Передача дополнительных данных в обработчик
<div id="content"></div>
<button id="showContent1">Показать контент 1</button>
<button id="showContent2">Показать контент 2</button>
...
<script>
$('#showContent1').on('click', {title:'Заголовок 1', content: 'Содержимое 1...'}, function(e){
var output = '<b>'+e.data.title+': </b>' + e.data.content;
$('#content').html(output);
});
$('#showContent2').on('click', {title:'Заголовок 2', content: 'Содержимое 2...'}, function(e){
var output = '<b>'+e.data.title+': </b>' + e.data.content;
$('#content').html(output);
});
</script>
Как повесить несколько событий на один элемент
$('#id').on('keyup keypress blur change', function(e) {
console.log(e.type); // тип события
});
// или так
var myFunction = function() {
...
}
$('#id')
.keyup(myFunction)
.keypress(myFunction)
.blur(myFunction)
.change(myFunction);
$("#id").on({
mouseenter: function() {
// обработчик события mouseenter...
},
mouseleave: function() {
// обработчик события mouseleave...
},
click: function() {
// обработчик события click...
}
});
$("#demo").click(function(){
console.log('1 обработчик события click');
});
$("#demo").click(function(){
console.log('2 обработчик события click');
});
var eventList = $._data($('#demo')[0], 'events');
console.log(eventList);
Программный вызов события
trigger- вызывает указанное событие у элемента.
triggerHandler- вызывает обработчик события, при этом само событие не происходит.
$('#header').on('click',
function() {
console('Произошёл клик на элементе #header');
}
});
// программный вызов события click элемента
$('#header').trigger('click');
// короткая запись данного вызова
$('#header').click();
// вызов обработчика события click у выбранного элемента
$('#header').triggerHandler('click');
jQuery - Событие загрузки страницы (ready)
$(function(){
// действия, которые необходимо выполнить после загрузки документа...
});
$(document).ready(function(){
// действия, которые необходимо выполнить после загрузки документа...
});
jQuery - Событие загрузки (load)
$(window).on('load', function() {
// действия после полной загрузки страницы...
});
<img id="image" src="image.png">
...
<script>
$('#image').on('load', function() {
console.log('Изображение загружено');
});
</script>
jQuery - События мыши
-
mousedown
-
mouseup
-
click
-
mousemove
-
wheel
-
hover
-
mouseenter
-
mouseover
-
mouseleave
-
mouseout
jQuery - Событие клик (click)
$(window).on('click', function(e) {
// обработка события click...
console.log('Нажата кнопка: ' + e.which); //1 - левая кнопка, 2 - средняя кнопка, 3 - правая
console.log('Координаты курсора: x = ' + e.pageX + ' ; y = ' + e.pageY);
});
$('.btn').on('click', function(){
// код обработчика события нажатия кнопки
...
});
$('.btn').click(function(){
...
});
<div class="message">
...<a class="hide-message" href="#">Скрыть блок через 5 секунд</a>...
</div>
...
<script>
$('.hide-message').click(function(e){
e.preventDefault();
var _this = this;
setTimeout(function(){
$(_this).closest('.message').hide();
}, 5000);
});
</script>
jQuery - Событие при наведении (hover)

-
вхождения (mouseenter, mouseover);
-
покидания (mouseleave, mouseout).
<ul id="list">
<li>Ручка</li>
<li>Карандаш</li>
<li>Линейка</li>
</ul>
...
<script>
$('ul>li').
mouseenter(function(){
// при вхождении в элемент
$(this).css('color','orange');
}).
mouseleave(function(){
// при покидании элемента
$(this).css('color','black');
});
</script>
$('ul>li').
mouseover(function(){
// при вхождении в элемент
$(this).css('color','orange');
}).
mouseout(function(){
// при покидании элемента
$(this).css('color','black');
});
<div id="count">Количество: <span>0</span></div>
...
<script>
$('#count').mouseenter(function(){
var count = parseInt($(this).find('span').text());
count++;
$(this).find('span').text(count);
});
</script>
$('ul>li').hover(
function(){
// при вхождении в элемент
$(this).css('color','orange');
},
function(){
// при покидании элемента
$(this).css('color','black');
}
);
$('h1').hover(function(){
console.log('Произошло события входа в элемент или выхода из него');
});
jQuery - Событие движения мыши
$('.target').mousemove(function(e) {
console.log('Вызван обработчик для события mousemove.');
console.log('Координаты относительно левого верхнего угла документа: ' + e.pageX + ', ' + e.pageY);
console.log('Координаты курсора внутри цели: ' + e.offsetX + ', ' + e.offsetY);
});
jQuery - Событие колёсика мыши (wheel)
$(window).on('wheel', function(e) {
// код обработчика (например)...
console.log('Количество прокрученных пикселей: ' + e.originalEvent.deltaY);
if (e.originalEvent.deltaY < 0){
console.log('Прокручиваем вверх');
} else {
console.log('Прокручиваем вниз');
}
});
jQuery – События клавиатуры
keydown -> keypress -> keyup
keydown(клавиша нажата, но ещё не отпущена);
keypress(событие генерируется для букв, цифр и других клавиш, за исключением управляющих) – предназначено для того чтобы получить код символа (события keydownи keyupпозволяют узнать только о коде клавиши, но не символа);
keyup(генерируется браузером при отпускании клавиши).
Например, напишем обработчик для прослушивания всех событий, которые происходят при нажатии клавиши:
<input id="target" type="text" value="">
...
<script>
$('#target').on('keydown keypress keyup', function(e) {
console.log('Тип события: ' + e.type); // keydown, keypress, keyup
console.log('Код нажатой клавиши или символа: ' + e.which); // код символа позволяет получить только keypress
console.log('Нажата клавиша Alt: ' + e.altKey);
console.log('Нажата клавиша Ctrl: ' + e.ctrlKey);
console.log('Нажата клавиша Shift: ' + e.shiftKey);
console.log('Нажата клавиша Cmd (osMac): ' + e.metaKey);
});
</script>
$(document).keypress("c",
function(e) {
if(e.ctrlKey) {
console.log('Нажато сочетание клавиш Ctrl+c');
}
});
$(document).keydown(function(e) {
// с поддержкой macOS X
if ((e.ctrlKey || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) {
// ваши действия...
}
}
<input id="name" type="text">
...
<script>
$('#name').
keydown(function(){
$(this).css('background-color', 'yellow');
}).
keyup(function(){
$(this).css('background-color, 'pink');
});
</script>
jQuery – События элементов формы
-
focus (focusin)
-
blur (focusout)
-
change
-
input (для текстовых элементов формы)
-
select
-
submit
jQuery - События получения и потери фокуса
<div id="demo"><input type="text"></div>
...
<script>
$('#demo').
focusin(function(){
$(this).css('background-color','orange');
})
.focusout(function(){
$(this).css('background-color','inherit');
});
</script>
$('#demo input').
focus(function(){
$(this).parent().css('background-color','orange');
})
.blur(function(){
$(this).parent().css('background-color','inherit');
});
jQuery - Событие изменения (change)
<input type="checkbox" id="agree">
<button type="submit" id="send" disabled>Отправить</button>
...
<script>
$(function() {
$('#agree').on('change', function(){
if (this.checked) {
$('#send').prop('disabled',false);
} else {
$('#send').prop('disabled',true);
}
});
});
</script>
<p id="result"></p>
<select id="list">
<option value="1">Первое значение</option>
<option value="2">Второе значение</option>
<option value="3">Третье значение</option>
</select>
...
<script>
$(function() {
$('#list').on('change', function(){
var value = $(this).val();
$('#result').text(value);
});
});
</script>
<select name="colors" multiple="multiple">
<option>Зелёный</option>
<option>Красный</option>
<option>Синий</option>
<option>Коричневый</option>
<option>Фиолетовый</option>
<option>Серый</option>
</select>
...
<script>
$('select[name="colors"]')
.change(function () {
var colors = [];
$('select[name="colors"] option:selected').each(function() {
colors.push($(this).text());
});
console.log('Выбранные цвета: ' + colors.join());
})
.change();
</script>
// list - id элемента change
$('#list').trigger('change');
// краткая запись
$('#list').change();
// вызов только обработчика события change
$('#list').triggerHandler('change');
<input type="text" name="name">
...
<script>
$(function() {
// событие изменения значения input (возникает только после потери фокуса)
$('input[name="name"]').on('change',
function(){
var value = $(this).val();
console.log(value);
});
});
</script>
$('input[name="name"]').on('input',function(){
var value = $(this).val();
console.log(value);
});
<textarea name="text"></textarea>
...
<script>
$('textarea[name="text"]').on('input',function(){
var value = $(this).val();
console.log(value);
});
</script>
<input type="radio" name="platform" checked="checked" value="windows">Windows
<input type="radio" name="platform" value="linux">Linux
<input type="radio" name="platform" value="macos">macOS
...
<script>
$(function() {
$('input[name="platform"]').on('change', function(){
var value = $(this).val();
console.log(value);
});
});
</script>
jQuery - Событие выбора (select)
$( "#target" ).select(function() {
console.log('Вызван обработчик события select');
});
jQuery – Событие отправки формы (submit)
<form id="feedback" action="action.php">
...
</form>
...
<script>
$('#feedback').submit(function(e) {
// отменить отправку формы
e.preventDefault();
// другие действия, например, для отправки формы по ajax...
});
</script>
$('#feedback').submit();
$('#feedback').trigger('submit');
jQuery - Событие прокрутки (scroll)
// краткая запись функции
$(window).scroll(function() {
// действия при скроллинге страницы...
if ($(this).scrollTop()>200) {
$('.scrollup').fadeIn();
} else {
$('.scrollup').fadeOut();
}
});
jQuery - Событие изменения размеров окна (resize)
$(window).resize(function() {
$('body').append('<p>Ширина x Высота = ' + window.innerWidth + ' x ' + window.innerHeight + '</p>');
});
jQuery - Отмена стандартного поведения события
$('a.service').on('click',function(e){
//отменяем стандартное действие браузера
e.preventDefault();
// действия, которые будет выполнять ссылка
...
});
Что такое всплытие и как его остановить

<div class="mark">Некоторый текст...<span class="mark">фрагмент...</span>...продолжение...</div>
...
<script>
$('.mark').on('hover',
function(e){
e.stopPropagation();
$(this).css('color',orange);
},
function(e){
e.stopPropagation();
$(this).css('color',black);
}
});
</script>
$('a').on('click', function(e){
//e.preventDefault();
//e.stopPropagation();
...
return false;
});
Добавление событий к динамически созданным объектам
$(document).on(eventName, selector, handler);
// document или любой другой существующий родительский элемент
// eventName - имя события
// selector - селектор, осуществляющий фильтрацию потомков, для которых необходимо запустить обработчик события
// handler - обработчик события
<button id="addButton" type="button">Добавить кнопку</button>
<script>
// при нажатии на элемент с id="addButton" добавить в начало страницы новую кнопку
$('#addButton').on('click', function(e) {
$('body').prepend('<button class="deleteMe" type="button">Удалить меня</button>');
});
// добавить событие click, которое будет выполнено для элементов, которых ещё нет на странице
$(document).on('click','.deleteMe', function() {
$(this).remove();
});
</script>
$(document).on('click','#comment a',function(e) {
if(!(location.hostname === this.hostname || !this.hostname.length)) {
e.preventDefault();
location.href='away?link='+encodeURIComponent($(this).attr('href'));
}
});
jQuery - Удалить обработчик события
$('.link').off();$('.link').off('link');$('.link').off('click','**');// добавление делегированного события
$('ul').on('click','li', function(){
// выводим в консоль контент элемента li
console.log($(this).text());
});
// удаление делегированного события
$('ul').off('click','li');
$('body').on('click.modal', '.show', openModal);Создание пользовательского события
<div class="module">
<p>...</p>
<button class="success" type="button">Вызвать пользовательское событие highlight (цвет зелёный)</button>
<button class="error" type="button">Вызвать пользовательское событие highlight (цвет красный)</button>
</div>
...
<script>
// добавляем ко всем элементам p пользовательское событие, которое будет изменять цвет текста и его содержимое
// получения данных, переданных методом trigger, осуществим посредством аргументов color и title
$('.module p').on('highlight', function(e, color, title) {
$(this).css('color',color);
$(this).text('Вызвано пользовательское событие highlight ' +title);
});
// при нажатии на элемент с классом success вызвать кастомное событие highlight и передать ему параметры
$('.success').click(function(){
// используя второй аргумент передадим данные в обработчик события
$(this).closest('.module').find('p').trigger('highlight',['green','(цвет зелёный)']);
});
// при нажатии на элемент с классом error вызвать кастомное событие highlight и передать ему параметры
$('.error').click(function(){
// используя второй аргумент передадим данные в обработчик события
$(this).closest('.module').find('p').trigger('highlight',['red','(цвет красный)']);
});
</script>
Управление HTML5-плеером с помощью JavaScript
Создание своего видеопроигрывателя
<video id="videoPlayer" width="600" height="400">
<source src="video.mp4" type="video/mp4">
<source src="video.ogv" type="video/ogg"></video><div>
<button onclick="play()">Play</button>
<button onclick="pause()">Pause</button>
<button onclick="stop()">Stop</button>
<button onclick="speedUp()">Быстрей</button>
<button onclick="slowDown()">Медленее</button>
<button onclick="normalSpeed()">Нормальная скорость</button></div>
var video;
var display;
window.onload = function() {
video = document.getElementById("videoPlayer");
};
function play() {
video.play();
}
function pause() {
video.pause();
}
function stop() {
video.pause();
video.currentTime = 0;
}
Функции других кнопок управления воспроизведением не совсем стандартные - они используются для регулирования скорости воспроизведения путем значения свойства playbackRate. Например, при значении playbackRate равным 2 видео проигрывается вдвое быстрее нормальной скорости, но с откорректированной высотой тона, вследствие чего звук воспроизводится нормально, только вдвое быстрее.
Это замечательная возможность для ускоренного просмотра медленных видеоинструкций. Аналогично, при значении playbackRate равном 0.5 видео проигрывается со скоростью, составляющей половину нормальной скорости. При значении playbackRate равном -1 видео должно проигрываться с нормальной скоростью, только в обратном направлении, но браузеры сталкиваются с проблемой реализации этого режима должным образом. Код для реализации этих функций следующий:
function speedUp() { video.play(); video.playbackRate = 2; }
function slowDown() { video.play(); video.playbackRate = 0.5; }
function normalSpeed() { video.play(); video.playbackRate = 1; }
<div class="barContainer">
<div id="durationBar">
<div id="positionBar">
<span id="displayStatus">0</span>
</div>
</div>
</div>
<progress>. Но уровень браузерной поддержки элемента <progress> все еще низкий, намного ниже, чем возможности видео HTML5. Поэтому в этом примере подобно выглядящий индикатор создается с помощью двух элементов <div>.Далее приведены правила таблицы стилей, которые устанавливают размер индикаторов и окрашивают их в соответствующие цвета:
#positionBar { height: 30px; color: white; font-weight: bold; background: steelblue; text-align: center; }
#positionBar span { display: inline-block; margin-top: 5px; }
#durationBar { border: solid 1px black; width: 100%; margin-bottom: 5px; }
<video id="videoPlayer" onTimeUpdate="progressUpdate()">
function progressUpdate() {
// Устанавливаем позицию воспроизведения
var positionBar = document.getElementById("positionBar");
positionBar.style.width = (video.currentTime / video.duration * 100) + "%"; // Заполняем текстовую надпись текущим значением
displayStatus = document.getElementById("displayStatus");
displayStatus.innerHTML = (Math.round(video.currentTime*100)/100) + " сек";
}

Можно сделать индикатор выполнения немного замысловатей, добавив индикатор загрузки, показывающий объем содержимого, загруженного и помещенного в буфер в данный момент. Эта возможность уже имеется в проигрывателях, встроенных в браузеры. Чтобы добавить этот индикатор, нужно обрабатывать событие onProgress и работать со свойством seekable. Дополнительную информацию о свойствах, методах и событиях элемента <video> смотрите на странице msdn.microsoft.com.
Проигрыватели на JavaScript
Если вы не хотите ни от кого зависеть в своей работе, то можете создать свой аудио- или видеопроигрыватель с чистого листа. Но это будет очень трудоемким проектом, особенно если вы хотите обеспечить разные вычурные возможности, наподобие интерактивного списка воспроизведения. К тому же, если за вашей спиной нет небольшого дизайнерского отдела, то существует отчетливая вероятность, что конечный продукт ваших усилий будет иметь слегка уродливый внешний вид (как показано выше ;).
К счастью, для веб-дизайнеров, блуждающих в поисках идеального HTML5-проигрывателя, имеется лучший вариант. Вместо того чтобы разрабатывать мультимедийный проигрыватель самому, можно взять бесплатный, настраиваемый посредством JavaScript проигрыватель в Интернете. Два из них — это VideoJS и, для фанатов библиотеки jQuery - jPlayer. Оба проигрывателя легковесные, удобны в использовании, а также имеют функцию смены скинов с помощью таблицы стилей. Например сервис VideoJS позволяет создать вручную скин медиаплеера:

<!DOCTYPE html>
<html>
<head>
<title>...</title>
<link href="video-js.css" rel="stylesheet" type="text/css">
<script src="video.js"></script>
</head>
...
Затем используется обычный элемент <video> с несколькими элементами <source> и резервным решением Flash. (Для резервного Flash-решения в проигрывателе VideoJS используется Flowplayer, но его можно удалить и использовать другой Flash-проигрыватель.) По сути, единственная разница между обычной страницей HTML5 и страницей с проигрывателем VideoJS состоит в том, что в последней нужно использовать специальный класс в элементе <video>:
<video class="video-js vjs-default-skin"...Учебник NodeJS 16+
Введение в NodeJS
Данный учебник посвящен NodeJS - серверному JavaScript. Перед изучением данного учебника вам уже необходимо: знать язык JavaScript, иметь установленный NodeJS, представлять себе работу протокола HTTP, уметь работать с npm, уметь работать с терминалом, уметь работать с асинхронностью, знать, что такое CommonJS и ES6 модули в JavaScript.
NodeJS - это обычный JavaScript, но работающий не в браузере на клиенте), а на сервере (на бэке). NodeJS представляет собой альтернативу языкам PHP, Python, и другим, работающим на сервере. При этом преимуществом NodeJS будет то, что JavaScript будет использоваться как на клиенте, так и на сервере. Это значит, что вам не нужно будет переключать свое внимание между двумя языками, да и вообще - для полноценного создания сайта вам потребуется знать лишь один язык.
Запуск NodeJS
В любом месте создайте файл с расширением js, например, app.js. Выведите в этом файле что-нибудь в консоль с помощью команды console.log:
console.log('test');
Откройте папку с вашим файлом в терминале. Напишите в нем следующую команду:
node app.js
В результате NodeJS выполнит код этого файла и вы увидите вывод в консоль.
Можно, конечно же, выполнять любые операции, которые возможно в JavaScript:
let a = 3; let b = 4; console.log(a + b);
В вашем файле запустите цикл и его помощью выведите в консоль числа от 1 до 10.
Таймеры в NodeJS
Давайте запустим таймер, который будет каждую секунду выводить что-нибудь в консоль:
setInterval(function() { console.log('!'); }, 1000);
Запущенный нами таймер будет работать вечно: пока мы не закроем терминал, либо не нажмем в нем Ctrl + С (попробуйте).
Запустите таймер, который каждую секунду будет выводить в консоль текущий момент времени.
Пусть дана переменная, в которой изначально хранится число 1. Запустите таймер, который каждую секунду будет увеличивать значение этой переменной на единицу и выводить это значение в консоль.
Модули ES в NodeJS
По умолчанию NodeJS использует модули по стандарту CommonJS. Так сложилось исторически. Однако, начиная с версии 13.2.0 появилась полноценная поддержка ES модулей. Так как ES модули - это официальный стандарт модулей, то я рекомендую вам пользоваться именно им.
Чтобы перейти на модули ES вам нужно разместить в папке с запускаемым вами файлом файл package.json со следующим содержимым:
{ "type": "module" }
Пример
Давайте для примера сделаем модуль math для математических операций. Разместим его код в файле math.js:
function square(num) { return num * num; } function cube(num) { return num * num * num; }
Выполним экспорт наших функций:
export function square(num) { return num * num; } export function cube(num) { return num * num * num; }
Импортируем теперь этот модуль в файл index.js:
import { square, cube } from './math.js';
Воспользуемся функциями нашего модуля:
let res = square(2) + cube(3); console.log(res);
Встроенные модули
Аналогичным образом импортируются встроенные модули. Например, импортируем модуль fs для работы с файловой системой:
import fs from 'fs';
Не обязательно импортировать все функции модуля. Можно импортировать только нужные нам:
import { open, read, close } from 'fs';
Подключение установленного через npm
Аналогичным образом импортируются модули, установленные через npm. Давайте для примера установим библиотеку underscore:
npm install underscore
Импортируем ее:
import _ from 'underscore';
В настоящее время в документациях почти всех библиотек в инструкциях подключение описано в стиле CommonJS. Вы, однако, можете сами переделать его на ES и подключить к своему проекту.
Установите библиотеку lodash. Подключите ее себе в проект и используйте несколько методов из этой библиотеки.
Подключение модуля fs в NodeJS
Сейчас мы научимся работать с файловой системой. За это в NodeJS отвечает встроенный модуль fs, который необходимо импортировать:
import fs from 'fs';
Каждый метод модуля fs существует в двух вариантах: в синхронном и в асинхронном.
Синхронный вариант
Синхронный вариант работы проще, однако он блокирует весь скрипт на время своего выполнения. К примеру, если у вас выполняется чтение файла, то весь остальной код будет ожидать, пока файл не прочитается.
При этом при чтении файла в основном загружается жесткий диск, а не процессор. То есть процессор мог бы выполнять наш код дальше, но вынужден ожидать загрузки файла.
Это имеет значение, когда у вас на NodeJS в интернете развернут сайт, на который заходят посетители.
Предположим, что при каждом заходе любого пользователя на наш сайт мы должны открыть некоторый файл. Если мы будем делать это синхронно, то при открытии одного файла все остальные пользователи нашего сайта будут ожидать пока этот файл откроется и только потом выполнение скрипта продолжится для них дальше.
В связи с этим синхронный вариант работы используется очень редко, в основном в тех случаях, когда вы делаете какой-то локальный скрипт, разворачиваемый на компьютере пользователя.
Асинхронный вариант
Работать с асинхронным вариантом сложнее, но его преимуществом является то, что он не блокирует скрипт.
Это значит, что пока с жесткого диска будет скачиваться файл, наш скрипт сможет выполнять другие операции, что существенно повышает быстродействие скрипта.
Асинхронный стиль в целом характерен для NodeJS, не только при работе с файлами. Данный подход позволяет существенно повысить быстродействие скриптов.
Асинхронная работа с fs через коллбэки в NodeJS
Давайте теперь рассмотрим асинхронную работу с файловой системой. Как правило, в NodeJS все методы модуля fs существуют в двух вариантах: в синхронном и асинхронном.
Например, для синхронного чтения файла используется метод readFileSync, а для асинхронного - readFile. Аналогично для записи файла существует пара writeFileSync и writeFile.
Асинхронное чтение файла
Метод readFile первым параметром принимает имя или путь к файлу, вторым параметром - кодировку, а третьим - коллбэк, который выполнится после чтения файла.
В коллбэк следует передавать два параметра. В первый параметр попадет объект с ошибкой, если она произойдет, а во второй - текст прочитанного файла.
Давайте для примера прочитаем текст какого-нибудь файла:
fs.readFile('readme.txt', 'utf8', function(err, data) { console.log(data); });
Дан файл с числом. Прочитайте этот файл и выведите в консоль квадрат этого числа.
Проверка асинхронности
Можно убедится в том, что чтение файла происходит асинхронно. Для этого выведем что-нибудь в консоль после работы с методом readFile:
fs.readFile('readme.txt', 'utf8', function(err, data) { console.log(data); }); console.log('!!!');
Как вы уже знаете, коллбэк выполнится, когда файл будет прочитан. А пока файл читается, код скрипта будет выполнятся дальше. Это значит, что в консоли сначала появится результат второго console.log, а потом первого.
Проверьте, что код после метода readFile будет выполнен раньше, чем будет прочитан файл.
Обработка исключительных ситуаций
Так как наш код асинхронный, то исключительные ситуации нельзя поймать через try-catch. Для обработки исключений в коллбэке предназначен первый параметр. Этот параметр будет содержать null, если исключения не случилось, или объект с ошибкой, если исключение произошло.
Давайте допишем код коллбэка так, чтобы он обрабатывал исключительные ситуации:
fs.readFile('readme.txt', 'utf8', function(err, data) { if (!err) { console.log(data); } else { console.log('ошибка', err); } });
Попробуйте прочитать несуществующий файл. Убедитесь, что при этом произойдет исключительная ситуация. Допишите ваш код так, чтобы он обрабатывал эту ситуацию.
Асинхронная запись файла
Асинхронная запись текста в файл выполняется аналогично:
fs.writeFile('readme.txt', 'text', function(err) { if (err) { console.log('ошибка'); } });
С помощью цикла создайте 10 файлов, содержащих целые числа от 1 до 10.
Асинхронное чтение нескольких файлов
Пусть у нас есть два файла с числами. Давайте найдем произведение этих чисел. Очевидно, что для этого нам нужно прочитать оба этих файла.
Но, так как код асинхронный, нам нужно читать второй файл в коллбэке первого:
fs.readFile('readme1.txt', 'utf8', function(err, data1) { if (!err) { fs.readFile('readme2.txt', 'utf8', function(err, data2) { if (!err) { console.log(data1 * data2);
} else {
console.log('ошибка чтения файла readme2'); } }); } else { console.log('ошибка чтения файла readme1'); } });
Даны три файла с числами. Выведите в консоль сумму этих чисел.
Даны пять файлов с числами. Выведите в консоль сумму этих чисел.
Асинхронное чтение и запись файла
Предположим нам нужно прочитать файл, сделать его текстом операцию и записать обратно в этот или другой файл. В этом случае запись в файл нужно будет делать в коллбэке чтения:
fs.readFile('readme.txt', 'utf8', function(err, data) { if (!err) { fs.writeFile('readme.txt', data + '!', function(err) { if (err) { console.log('ошибка записи файла'); } });
} else {
console.log('ошибка чтения файла'); } });
Дан файл с числом. Запишите в этот файл квадрат этого числа.
Даны три файла с числами. Запишите в новый файл сумму этих чисел.
Стрелочные функции
Как правило коллбэки в NodeJS делают с помощью стрелочных функций. Это сокращает код, но несколько затрудняет понимание с непривычки.
Давайте переделаем предыдущий код на стрелочные функции:
fs.readFile('readme.txt', 'utf8', (err, data) => { if (!err) { fs.writeFile('readme.txt', data + '!', err => { if (err) { console.log('ошибка записи файла'); } });
} else {
console.log('ошибка чтения файла'); } });
Дан код:
fs.readFile('readme1.txt', 'utf8', function(err, data1) { if (!err) { fs.readFile('readme2.txt', 'utf8', function(err, data2) { if (!err) { fs.writeFile('readme.txt', data1 + data2, function(err) { if (err) { console.log('ошибка записи файла'); } });
} else {
console.log('ошибка чтения файла readme2'); } });
} else {
console.log('ошибка чтения файла readme1'); } });
Перепишите его через стрелочные функции.
Синхронное чтение и запись файлов в NodeJS
В данном уроке мы разберемся с синхронной работой с файлами, так как она более простая для понимания.
Синхронное чтение файла
Для синхронного чтения файла используется метод readFileSync. Первым параметром он принимает имя файла или путь к файлу, а вторым - кодировку файла, как правило это utf8.
Пусть в папке с нашим скриптом располагается файл readme.txt. Давайте прочитаем его текст и выведем на экран:
let text = fs.readFileSync('readme.txt', 'utf8'); console.log(text);
Сделайте два файла, текстом которых будут некоторые числа. Напишите скрипт, который прочитает числа из файлов и выведет в консоль сумму этих чисел.
Синхронная запись файла
Для синхронной записи в файл используется функция writeFileSync. Первым параметром она принимает имя файла или путь к нему, а вторым - текст, который мы хотим записать в этот файл:
fs.writeFileSync('readme.txt', 'text');
Если файл уже существует, то его текст будет перезаписан. А если файл не существует - то он будет автоматически создан.
Дан объект:
let obj = { 'file1.txt': 'text1', 'file2.txt': 'text2', 'file3.txt': 'text3', }
С помощью цикла для каждого элемента объекта создайте файл, именем которого будет свойство элемента, а текстом - значение свойства.
Дан файл с текстом. Запустите таймер, который каждые 5 секунд в конец этого файла будет записывать восклицательный знак.
Синхронные чтение и запись файла
Можно прочитывать данные из файла, совершать над ними какую-нибудь операцию, а затем записывать обратно.
Для примера давайте прочитаем текст из файла readme.txt, добавим ему в конец знак ! и запишем измененный текст обратно в файл:
let text = fs.readFileSync('readme.txt', 'utf8'); fs.writeFileSync('readme.txt', text + '!');
Дан файл, в тексте которого записано некоторое число. Напишите скрипт, который прочитает число из файла, прибавит к нему единицу и запишет новое число обратно в файл.
Даны 3 файла с числами. Напишите скрипт, который прочитает числа из файлов, найдет их сумму и запишет ее в новый файл.
Исключительные ситуации
При работе с файловой системой могут возникать различные исключительные ситуации. Например, файл может отсутствовать, может отказать жесткий диск, диск может быть переполнен, диск может сломаться и не отвечать, у вас может не быть прав для записи в определенную папку и так далее.
Поэтому любую работу с файловой системой необходимо оборачивать в try-catch:
try { let text = fs.readFileSync('readme.txt', 'utf8'); console.log(text); } catch (err) { console.log('при чтении файла возникла ошибка', err); }
Попробуйте прочитать несуществующий файл. Убедитесь, что при этом произойдет исключительная ситуация. Допишите ваш код так, чтобы он обрабатывал эту ситуацию.
Асинхронная работа с fs через then в NodeJS
С методами модуля fs асинхронно можно работать не только через коллбэки, но и через промисы. Для этого есть специальное свойство promises, содержащее в себе промисные аналоги методов для работы с файловой системой. К примеру, для метода fs.readFile его промисный аналог будет fs.promises.readFile.
Чтение файлов
Давайте выведем в консоль содержимое какого-нибудь файла:
fs.promises.readFile('readme.txt', 'utf8').then(data => { console.log(data); });
Пусть в файле записано число. Прочитайте этот файл и выведите в консоль сумму цифр этого числа.
Обработка исключений
Добавим теперь обработку исключительных ситуаций:
fs.promises.readFile('readme.txt', 'utf8').then(data => { console.log(data); }).catch(err => { console.log('ошибка'); });
Попробуйте прочитать несуществующий файл. Убедитесь, что при этом произойдет исключительная ситуация. Допишите ваш код так, чтобы он обрабатывал эту ситуацию.
Чтение и запись
Можно прочитать файл, что-то сделать с его текстом, а потом записать обратно:
fs.promises.readFile('readme.txt', 'utf8').then(data => { return fs.promises.writeFile('readme.txt', data + '!'); }).catch(err => { console.log('ошибка'); });
Пусть в файле через запятую записаны числа. Сделайте скрипт, который запишет каждое из этих чисел в отдельный файл.
Массовая работа
Пусть у нас есть несколько файлов. Давайте прочитаем эти файлы, сольем их текст в одну строку и запишем ее в новый файл.
В отличие от коллбэков, в данном случае нам нет нужды выполнять чтение файлов по очереди. При работе с промисами мы можем записать все промисы для чтения файлов в массив, а потом воспользоваться Promise.all, чтобы осуществить запись в файл только тогда, когда все файлы будут прочитаны.
Давайте сделаем это. Пусть имена файлов у нас есть в виде массива:
let names = ['1.txt', '2.txt', '3.txt'];
Запустим цикл, в котором будем читать файлы, записывая промисы с результатами в массив:
let names = ['1.txt', '2.txt', '3.txt']; let files = []; for (let name of names) { files.push(fs.promises.readFile(name, 'utf8')); } console.log(files); // массив промисов
Имея такой массив, мы можем вызвать then только один раз, когда все промисы выполнятся:
Promise.all(files).then(data => { fs.promises.writeFile('res.txt', data.join('')); });
Добавим обработку исключительных ситуаций:
Promise.all(files).then(data => { fs.promises.writeFile('res.txt', data.join('')); }).catch(err => { console.log('что-то пошло не так'); });
Соберем весь наш код вместе:
let names = ['1.txt', '2.txt', '3.txt']; let files = []; for (let name of names) { files.push(fs.promises.readFile(name, 'utf8')); } Promise.all(files).then(data => { fs.promises.writeFile('res.txt', data.join('')); }).catch(err => { console.log('что-то пошло не так'); });
Пусть у вас есть 5 файлов с числами. Найдите сумму этих чисел и запишите в новый файл.
Сокращение fs.promises в NodeJS
Вспомним, как мы подключали модуль fs:
import fs from 'fs';
После такого подключения нам будут доступны промисные варианты методов этого модуля через fs.promises:
fs.promises.readFile('readme.txt', 'utf8').then(data => { console.log(data); });
Однако, писать свойство promises перед каждым нужным методом не очень удобно. Давайте поступим хитрее - запишем в переменную fs не сам модуль, а его часть с промисами:
import fs from 'fs/promises';
И теперь мы можем опускать раздражающее нас свойство:
fs.readFile('readme.txt', 'utf8').then(data => { console.log(data); });
Дан следующий код:
import fs from 'fs'; async function func() { let data = await fs.promises.readFile('readme.txt', 'utf8'); console.log(data); } func();
Упростите его согласно изученной теории.
Относительные пути в NodeJS
Как вы уже знаете, в параметр методов чтения или записи следует писать имя файла. Это, однако, работает только в том случае, если читаемый файл лежит в той же папке, в которой запускается наш скрипт.
Если же файл лежит в другом месте, то в параметр метода нужно писать путь к этому файлу.
Посмотрим на примерах.
Пример
Пусть у нас есть следующая структура файлов:
index.js
/directory/
readme.txt
Давайте прочитаем содержимое текстового файла. Для этого кроме имени файла нам понадобится указать еще и папку, в которой он лежит:
let path = 'directory/readme.txt'; let data = await fs.promises.readFile(path, 'utf8');
Пример
Пусть у нас есть следующая структура файлов:
/script/
index.js
readme.txt
В таком случае попытка прочитать наш файл, указав в качестве пути его имя, закончится ошибкой:
let path = 'readme.txt'; let data = await fs.promises.readFile(path, 'utf8'); // выдаст ошибку
Почему выдается ошибка? Дело в том, что мы в параметр функции написали имя файла. Это значит, что читаемый файл должен размещаться в той же папке, что и запускаемый.
Однако, наш читаемый файл находится на уровень выше, то есть в той папке, которая содержит папку со скриптом.
В таком случае мы должны явно указать в пути к файлу, что этот файл нужно искать на уровень выше. Для этого перед именем файла следует написать ../. Сделаем это:
let path = '../readme.txt'; let data = await fs.promises.readFile(path, 'utf8');
Пример
Пусть у нас есть следующая структура файлов:
/script/
index.js
/directory/
readme.txt
В этом случае при чтении файла мы сначала выйдем на уровень выше, а затем укажем путь к нашему файлу относительно этого уровня:
let path = '../directory/readme.txt'; let data = await fs.promises.readFile(path, 'utf8');
Пример
Пусть у нас есть следующая структура файлов:
/script/
/test/
index.js
/directory/
readme.txt
В этом случае нам потребуется выйти наверх два раза:
let path = '../../directory/readme.txt'; let data = await fs.promises.readFile(path, 'utf8');
Практические задачи
Напишите код, который прочитает содержимое текстового файла:
index.js
/dir1/
/dir2/
readme.txt
Напишите код, который прочитает содержимое текстового файла:
/script/
index.js
/dir1/
/dir2/
readme.txt
Напишите код, который прочитает содержимое текстового файла:
/script1/
/script2/
index.js
/dir/
readme.txt
Напишите код, который прочитает содержимое текстового файла:
/script1/
/script2/
/script3/
index.js
/dir1/
/dir2/
/dir3/
readme.txt
Имя папки со скриптом в NodeJS
Если ваш NodeJS работает в стиле CommonJS, то в файлах с вашими скриптами будет доступна константа __dirname:
console.log(__dirname);
В ES модулях, однако, эта константа была убрана. Впрочем, ее несложно получить самому. Сделаем для этого файл __dirname.js, экспортирующий нужный нам путь к папке со скриптом:
import { dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); export default __dirname;
Теперь в нужно исполняемом файле мы можем импортировать созданный нами модуль и получить нужную нам константу:
import __dirname from './__dirname.js'; console.log(__dirname);
Создайте файл __dirname.js. Подключите его к своему исполняемому файлу. Выведите в консоль путь к вашей рабочей папке.
Асинхронная работа с fs через async-await в NodeJS
Давайте теперь вместо then будем использовать альтернативный синтаксис async-await. В этом случае мы получим возможность обращаться с нашим асинхронным кодом также просто и удобно, как с синхронным.
Давайте сразу смотреть на примерах. Прочитаем текст файла и выведем его в консоль:
async function func() { let data = await fs.promises.readFile('readme.txt', 'utf8'); console.log(data); } func();
Добавим обработку ошибок:
async function func() { try { let data = await fs.promises.readFile('readme.txt', 'utf8'); console.log(data); } catch (err) { console.log('что-то пошло не так'); } } func();
Прочитаем три файла, сольем их текст и выведем в консоль:
async function func() { try { let data1 = await fs.promises.readFile('1.txt', 'utf8'); let data2 = await fs.promises.readFile('2.txt', 'utf8'); let data3 = await fs.promises.readFile('3.txt', 'utf8'); console.log(data1 + data2 + data3); } catch (err) { console.log('что-то пошло не так'); } } func();
Запишем текст трех файлов в новый файл:
async function func() { try { let data1 = await fs.promises.readFile('1.txt', 'utf8'); let data2 = await fs.promises.readFile('2.txt', 'utf8'); let data3 = await fs.promises.readFile('3.txt', 'utf8'); await fs.promises.writeFile('res.txt', data1 + data2 + data3); } catch (err) { console.log('что-то пошло не так'); } } func();
Пусть имена наших файлов записаны в массиве. Давайте прочитаем данные наших файлов в цикле, а затем запишем их в новый файл:
async function func() { try { let names = ['1.txt', '2.txt', '3.txt']; let data = []; for (let name of names) { data.push(await fs.promises.readFile(name, 'utf8')); } await fs.promises.writeFile('res.txt', data.join('')); } catch (err) { console.log('что-то пошло не так'); } } func();
Даны два файла с числами. Найдите сумму этих чисел и запишите результат в третий файл.
Дан массив имен файлов. Переберите этот массив циклом и создайте файлы с этими именами, записав при создании в каждый файл случайное число. После этого в цикле прочитайте содержимое всех файлов и найдите сумму их чисел. Запишите ее в новый файл.
Проверка существования файла в NodeJS
Давайте научимся проверять существование файла или папки. В современном NodeJS это делается особым образом через метод fs.access, проверяющий возможность доступа к файлу.
Для начала импортируем асинхронную часть модуля fs:
import fs from 'fs/promises';
Теперь из общей части модуля fs импортируем специальные константы:
import { constants } from 'fs';
После этого в переменной constants будет специальный объект с константами, содержащими специальные флаги, которые мы будем передавать параметром в метод access, изменяя его поведение.
Следующий флаг используется для проверки существования файла:
constants.F_OK
Давайте с помощью этого флага проверим существование файла test.txt:
fs.access('test.txt', constants.F_OK);
Наш метод access своим резульатом возвращает промис. Если файл существует, но промис зарезолвится, а если нет - то зареджектится:
fs.access('test.txt', constants.F_OK).then(() => { console.log('file exists'); }).catch(() => { console.error('file does not exists'); });
Давайте перепишем наш код через await. Для этого обернем вызов access в конструкцию try-catch:
try { await fs.access('test.txt', constants.F_OK); console.log('file exists'); } catch { console.error('file does not exists'); }
Проверьте, существует ли файл test.txt. Если существует, прочитайте его текст.
Другие флаги
В наш метод access можно передавать и другие флаги. К примеру, следующий флаг используется для проверки разрешения на чтение файла:
constants.R_OK
А следующий флаг используется для проверки разрешения на запись файла:
constants.W_OK
Давайте выполним проверку файла на возможность чтения:
try { await fs.access('test.txt', constants.R_OK); console.log('can read'); } catch { console.error('cannot read'); }
А теперь выполним проверку файла на возможность записи:
try { await fs.access('test.txt', constants.W_OK); console.log('can write'); } catch { console.error('cannot write'); }
А теперь проверим файл и на то, и на другое:
try { await fs.access('test.txt', constants.R_OK | constants.W_OK); console.log('can access'); } catch { console.error('cannot access'); }
Работа с каналами в NodeJS
Ситуация, в которой нам нужно прочитать данные из одного потока и записать их в другой, является достаточно распространенной. Для упрощения такой операции придуманы каналы (англ. pipes).
Давайте посмотрим, как это делается. Пусть у нас есть поток чтения и поток записи:
let readableStream = fs.createReadStream('readme.txt', 'utf8'); let writeableStream = fs.createWriteStream('writeme.txt');
Сделаем так, чтобы поток чтения сразу перенаправлялся в поток записи. Для этого у потоков чтения существует метод pipe:
readableStream.pipe(writeableStream);
С помощью канала прочитайте файл и запишите его данные в другой файл.
Цепочки
Методы pipe можно вызывать цепочкой друг за другом. При этом каждый вызов метода в цепочке позволяет выполнять над данными некоторые операции:
readableStream.pipe(операция).pipe(операция).pipe(операция);
Для примера давайте выполним архивацию файла. Для этого нам надо сначала считать файл, затем сжать данные и в конце записать сжатые данные в файл-архив. В этом нам поможет встроенная в NodeJS библиотека zlib:
import { createGzip } from 'zlib';
Теперь с помощью цепочки методов сначала заархивируем поток чтения, а потом запишем в поток записи:
readableStream.pipe(createGzip()).pipe(writeableStream);
Даны 10 файлов. Напишите код, который зархивирует каждый из этих файлов в свой архив.
Потоки чтения и записи в NodeJS
Представим, что у вас есть достаточно большой файл, скажем размером в 100 мегабайт. Пусть мы хотим что-то сделать с данными этого файла. Очевидно, что для этого нужно прочитать содержимое этого файла в переменную:
let data = await fs.promises.readFile('readme.txt', 'utf8'); console.log(data);
Проблема в том, что в таком случае все 100 мегабайт нашего файла попадут в оперативную память сервера. Поэтому попытка одновременно прочитать десяток-другой таких файлов запросто исчерпает доступную нам память сервера, что приведет к его падению.
Для решения такой проблемы придуманы потоки. Потоки позволяют прочитывать файл по кусочкам. Давайте посмотрим, как это делается. Для начала создадим поток чтения с помощью метода createReadStream:
let readableStream = fs.createReadStream('readme.txt', 'utf8');
Теперь прочитаем наш файл по кусочкам:
readableStream.on('data', function(chunk) { console.log(chunk); });
Сделайте файл достаточно большого размера. Прочитайте его по кусочкам и выведите каждый кусочек в консоль.
Потоки записи
Можно также создавать потоки записи. Это делается с помощью метода createWriteStream:
let writeableStream = fs.createWriteStream('writeme.txt');
Давайте что-нибудь запишем в наш поток с помощью метода write:
writeableStream.write('text1\n'); writeableStream.write('text2\n'); writeableStream.write('text3\n');
Завершим процедуру записи с помощью метода end:
writeableStream.end();
Запишите в файл столбец чисел от одного до миллиона.
Чтение и запись
Можно прочитывать один файл, записывая по частям его данные в другой файл:
let readableStream = fs.createReadStream('readme.txt', 'utf8'); let writeableStream = fs.createWriteStream('writeme.txt'); readableStream.on('data', function(chunk) { writeableStream.write(chunk); });
Дан файл большого размера. Напишите код, который сделает три копии этого файла.
Создание HTTP сервера на NodeJS
Давайте теперь развернем HTTP сервер на NodeJS . Этот сервер будет принимать запросы от браузера и отдавать в ответ HTML код страниц.
Для начала нам нужно импортировать модуль http:
import http from 'http';
После этого мы можем стартовать наш сервер с помощью метода createServer:
http.createServer();
Этот метод параметром принимает коллбэк. Этот коллбэк будет выполнятся каждый раз, когда кто-то через браузер обращается к нашему сайту:
http.createServer(() => { });
В нашем коллбэке мы должны определить два параметра. В первый параметр попадет объект с данными запроса пользователя, а во второй параметр - объект, с помощью которого мы сфомируем наш ответ, отправляемый в браузер:
http.createServer((request, response) => { });
С помощью метода write мы можем говорить, какой текст отправить в браузер:
http.createServer((request, response) => { response.write('text1'); response.write('text2'); response.write('text3'); response.end(); });
С помощью метода end мы командуем завершить наш ответ и отправить его в браузер:
http.createServer((request, response) => { response.write('text1'); response.write('text2'); response.write('text3'); response.end(); });
После запуска сервера мы должны сказать, на каком порте наш сервер будет ожидать запросы от браузера. Это делается с помощью метода listen. Давайте укажем традиционный порт 3000:
http.createServer((request, response) => { response.write('text1'); response.write('text2'); response.write('text3'); response.end(); }).listen(3000);
Теперь мы можем обратиться к нашему серверу через браузер, набрав http://localhost:3000, где после двоеточия указан заданный нами порт.
Разверните сервер, отправляющий в браузер какой-нибудь текст.
Остановка сервера
В предыдущих уроках наши скрипты отрабатывали сразу и после этого консоль была готова к вводу новых команд. С сервером это будет не так, так как он представляет собой некоторый исполняемый процесс.
Чтобы завершить такой процесс, вам нужно в терминале нажать клавиши Ctrl + C. В этом случае сервер остановится и затем его можно будет запустить опять.
Остановите запущенный сервер.
Настройка ответа сервера на NodeJS
С помощью свойства statusCode мы указываем код HTTP ответа. Как правило это код 200, сообщающий браузеру о том, что все ок и страница найдена:
http.createServer((request, response) => { response.statusCode = 200; response.write('hello world'); response.end(); });
Поменяйте код ответа на 404. В качестве текста страницы выведите текст о том, что страница не найдена.
HTTP заголовки
С помощью метода setHeader можно отправлять HTTP заголовки. Для примера давайте отправим заголовок Content-Language:
http.createServer((request, response) => { response.setHeader('Content-Language', 'ru'); response.statusCode = 200; response.write('hello world'); response.end(); });
Отправьте заголовок Cache-Control со значением 'no-cache'.
Отправка HTML
Пока, однако, браузер трактует наш ответ как обычный (plain) текст. Сделаем так, чтобы наш ответ трактовался как HTML. Для этого с помощью метода setHeader отправим соответствующий HTTP заголовок:
http.createServer((request, response) => { response.setHeader('Content-Type', 'text/html'); response.statusCode = 200; response.write('<b>hello world</b>'); response.end(); }).listen(3000);
Сделайте так, чтобы ваш сервер при запросе отдавал текущее время, оформленное в каком-нибудь теге.
Код ответа
Код ответа и заголовки можно объединить в одном методе writeHead. Первым параметром этот метод принимает код ответа, а вторым - объект с заголовками:
http.createServer((request, response) => { response.writeHead(200, {'Content-Type': 'text/html'}); response.end(); });
Дан следующий код:
http.createServer((request, response) => { response.setHeader('Content-Type', 'text/plain'); response.statusCode = 404; response.write('page not found'); response.end(); }).listen(3000);
Упростите его через метод writeHead.
Дан следующий код:
http.createServer((request, response) => { response.setHeader('Content-Type', 'text/html'); response.setHeader('Content-Language', 'ru'); response.statusCode = 200; response.write('{}'); response.end(); }).listen(3000);
Упростите его через метод writeHead.
Понимание работы сервера на NodeJS
Вам следует понимать, что наш сервер, будучи один раз запущенным, обрабатывает запросы всех пользователей нашего сайта. Этим подход NodeJS отличается от подхода PHP в котором скрипт запускается, отрабатывает и умирает.
В случае NodeJS получается, что функция-коллбэк нашего сервера вызывается на каждый запрос. Это значит, что внешние переменные этой функции будут общими для всех запросов.
Таким образом можно сделать, к примеру, счетчик запросов к нашему серверу и отдавать его значение каждому запросу:
let i = 0; http.createServer((request, response) => { response.setHeader('Content-Type', 'text/html'); response.statusCode = 200; response.write(String(++i)); response.end(); }).listen(3000);
Пусть изначально наш счетчик имеет значение 100. Каждый запрос уменьшайте это значение на единицу и отдавайте новое значение в браузер. Как только счетчик дойдет до нуля, выведите результатом запроса сообщение об этом.
Объект запроса Http сервера на NodeJS
Объект запроса request содержит данные запроса браузера к серверу. В этом объекте свойство url содержит адрес запрошенной страницы, свойство method содержит HTTP метод запроса, а свойство headers содержит массив отправленных заголовков.
Давайте выведем значения указанных свойств на экран:
http.createServer((request, response) => { console.log(request.url); console.log(request.method); console.log(request.headers); response.writeHead(200, {'Content-Type': 'text/html'}); response.write('text'); response.end(); }).listen(3000);
Пообращайтесь к серверу с различными URL. Посмотрите, что будет выводится в консоль в этом случае.
Выведите в консоль содержимое заголовка host.
Простой роутинг в NodeJS
Давайте теперь будем выдавать разное содержимое при обращении к разным URL:
http.createServer((request, response) => { if (request.url != '/favicon.ico') { if (request.url == '/page1') { response.writeHead(200, {'Content-Type': 'text/html'}); response.write('1'); response.end(); } if (request.url == '/page2') { response.writeHead(200, {'Content-Type': 'text/html'}); response.write('2'); response.end(); } if (request.url == '/page3') { response.writeHead(200, {'Content-Type': 'text/html'}); response.write('3'); response.end(); } } }).listen(3000);
Очевидно, что часть кода у нас дублируется. Давайте его упростим:
http.createServer((request, response) => { if (request.url != '/favicon.ico') { let text; if (request.url == '/page1') { text = '1'; } if (request.url == '/page2') { text = '2'; } if (request.url == '/page3') { text = '3'; } response.writeHead(200, {'Content-Type': 'text/html'}); response.write(text); response.end(); } }).listen(3000);
Как вы видите, у нас есть три адреса, доступных для запроса: /page1, /page2 и /page3. Давайте сделаем так, чтобы при обращении к любому другому адресу выдавалось сообщение о том, что страница не найдена:
http.createServer((request, response) => { if (request.url != '/favicon.ico') { let text; if (request.url == '/page1') { text = '1'; } else if (request.url == '/page2') { text = '2'; } else if (request.url == '/page3') { text = '3'; } else { text = 'page not found'; } response.writeHead(200, {'Content-Type': 'text/html'}); response.end(); } }).listen(3000);
Чтобы браузер и поисковые системы правильно обрабатывали отсутствие страницы, мы должны для существующей страницы отдавать статус 200, а для не существующей - 404. Давайте сделаем это:
http.createServer((request, response) => { if (request.url != '/favicon.ico') { let text; let status; if (request.url == '/page1') { text = '1'; status = 200; } else if (request.url == '/page2') { text = '2'; status = 200; } else if (request.url == '/page3') { text = '3'; status = 200; } else { text = 'page not found'; status = 404; } response.writeHead(status, {'Content-Type': 'text/html'}); response.end(); } }).listen(3000);
Можно упростить код следующим образом:
http.createServer((request, response) => { if (request.url != '/favicon.ico') { let text; let status = 200; if (request.url == '/page1') { text = '1'; } else if (request.url == '/page2') { text = '2'; } else if (request.url == '/page3') { text = '3'; } else { text = 'page not found'; status = 404; } response.writeHead(status, {'Content-Type': 'text/html'}); response.end(); } }).listen(3000);
Перепишите приведенный код через оператор switch-case.
Дан объект с URL-лами и соответствующими им текстами страниц:
let obj = { '/page1': '1', '/page2': '2', '/page3': '3', }
Сделайте сервер на основе этого объекта. При запросе существующего в объекте адреса отдавайте соответствующий текст, а при запросе отсутствующего - сообщение об ошибке и статус 404.
Отдача HTML страниц из файлов в NodeJS
Давайте теперь по запросу будем отдавать не какой-то текст, а корректно оформленную HTML страницу. Пусть такая страница хранится в файле page.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>my page</title>
</head>
<body>
<p> my first page on NodeJS </b>
</body>
</html>
Давайте по запросу прочитаем эту страницу из файла и отправим ее в браузер:
http.createServer(async (request, response) => { if (request.url != '/favicon.ico') { let text = await fs.promises.readFile('page.html', 'utf8'); response.writeHead(200, {'Content-Type': 'text/html'}); response.write(text); response.end(); } }).listen(3000);
Как вы видите, чтение файла осуществляется асинхронным образом. В этом случае в ожидании считывания файла с диска сервер сможет обрабатывать запросы других пользователей к нашему серверу.
Дан объект с URL-лами и соответствующими им именам HTML страниц:
let obj = { '/page1': 'file1.html', '/page2': 'file2.html', '/page3': 'file3.html', }
Сделайте сервер на основе этого объекта. При запросе существующего в объекте адреса отдавайте соответствующую страницу, а при запросе отсутствующего - сообщение об ошибке и статус 404.
Убираем двойной запрос HTTP сервера на NodeJS
При выводе URL сайта вы могли обратить внимание на то, что кроме запрошенного адреса в консоль выводится еще и '/favicon.ico'.
Дело в том, что при заходе на сайт браузер автоматически запрашивает фавиконку. Это приводит к тому, что к серверу отправляется два запроса.
Наиболее правильным действием здесь будет на запрос фавиконки отдавать файл с ней. Вы, однако, еще это делать не умеете, поэтому просто сделаем условие, отсекающее двойной вывод в консоль:
http.createServer((request, response) => { if (request.url != '/favicon.ico'){ console.log(request.url); // теперь выполнится один раз response.writeHead(200, {'Content-Type': 'text/html'}); response.write('text'); response.end(); } }).listen(3000);
Такой шаг не избавляет нас от двойного запроса (он всегда будет двойной), но раздражающий двойной вывод в консоль исчезнет.
В дальнейшем мы поправим наш код так, чтобы в браузер действительно отдавалась фавиконка.
Сделайте так, чтобы у вас не было двойного вывода в консоль.
Файлы ресурсов в NodeJS
Давайте теперь научимся отдавать браузеру файлы ресурсов. Под ресурсами понимаются файлы картинок, файлы CSS стилей, файлы клиентских скриптов и так далее.
Давайте рассмотрим работу с ресурсами на примере картинок. Пусть, например, по адресу /page.html мы хотим отдавать HTML файл, а по адресу /image.png - картинку.
Сделаем это:
http.createServer(async (request, response) => { if (request.url != '/favicon.ico') { let data; let type; if (request.url === '/page.html') { data = await fs.promises.readFile('page.html', 'utf8'); type = 'text/html'; } if (request.url === '/image.png') { data = await fs.promises.readFile('image.png'); type = 'image/png'; // правильно укажем mime-тип } response.writeHead(200, {'Content-Type': type}); response.write(data); response.end(); } }).listen(3000);
Создайте файл styles.css. Отдайте его по соответствующему запросу. Не забудьте правильно указать тип данных.
Создайте файл script.js. Отдайте его по соответствующему запросу. Не забудьте правильно указать тип данных.
Разместите у себя файл с фавиконкой, назвав его favicon.ico. Уберите в вашем коде условие для блокировки двойного запроса, а вместо этого отдавайте корректную фавиконку.
Автоматические запросы браузера
Рассмотрим следующую HTML страницу:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>my page</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<p> my first page on NodeJS </b> <img src="img.png">
</body>
</html>
Как вы видите, на данной странице подключается CSS файл, JavaScript файл, а также картинка. Это значит, что когда браузер получит и прочитает HTML код нашей страницы, он автоматически отправит серверу еще 3 запроса для получения указанных файлов.
Скопируйте приведенный код HTML страницы и разместите его в файле. Отдайте этот файл браузеру по соответствующему запросу. Сделайте так, чтобы браузер получил запрошенные им файлы ресурсов, на которые ссылается наша HTML страница.
Движок сайта на NodeJS
Давайте рассмотрим структуру типичного сайта:
<!DOCTYPE html>
<html>
<head>
<title>page title</title>
</head>
<body>
<div id="wrapper">
<header> common header <nav> common menu </nav> </header>
<main> page content </main>
<aside> common sidebar </aside>
<footer> common footer </footer>
</div>
</body>
</html>
Как вы видите, у страницы сайта как правило есть хедер, футер, сайдбары и контент.
При этом на многостраничном сайте все страницы сайта отличаются лишь контентом и тайтлом, а хедер, футер и сайдбары остаются одинаковым.
Это создает определенные неудобства в поддержке сайта: если у вас на сайте сто страниц, то у них будет сто одинаковых хедеров, раскиданных по ста файлам. Если вам что-то нужно будет поменять в хедере сайта - вам придется сделать это сто раз! Это, конечно же, не удобно.
Поэтому в современном сайтостроении принят другой подход: создается один файл шаблона сайта, содержащий все одинаковые места, а также места, в которые будут вставляться изменяемые части, например, тайтл и контент.
При этом тайл и контент страницы хранятся в отдельных файлах. То есть получается, что для каждой страницы сайта в файле хранится только изменяющиеся тайтл и контент, а общие хедер, футер и сайдбары хранятся в общем файле с шаблоном сайта.
Когда браузер запрашивает страницу сайта, то сервер по запрошенному URL получает файл с тайтлом и контентом, затем берет файл шаблона и подставляет тайл и контент в специально обозначенные места шаблона.
В результате получается готовая HTML страница со всеми частями, которая и отправляется в браузер.
Места для вставки изменяющихся частей в шаблоне обозначаются с помощью придуманных нами команд. Например, я могу сказать, что место вставки тайтла обозначим как {% get title %}, а место вставки контента как {% get content %}. С такими командами файл шаблона может иметь следующий вид:
<!DOCTYPE html>
<html>
<head>
<title>{% get title %}</title>
</head>
<body>
<div id="wrapper">
<header> common header </header>
<main> {% get content %} </main>
<aside> common sidebar </aside>
<footer> common footer </footer>
</div>
</body>
</html>
Реализация
Итак, давайте реализуем описанное. Для начала продумаем файловую структуру. Пусть в папке page лежат изменяющиеся части, причем тайтл и контент одной страницы в разных файлах в одной папке. Адрес их папки пусть соответствует URL страницы.
Пусть, к примеру, запрошен адрес /dir/name/. Тогда тайтл этой страницы будет лежать в файле /page/dir/name/title.html, а контент - в файле /page/dir/name/content.html.
В папке root разместим ресурсы сайта (для простоты их пока проигнорируем), а шаблон сайта - в файле layout.html.
Давайте теперь напишем код. Наш сервер при запросе должен прочитать файл с тайтлом, файл с контентом и файл с шаблоном. Затем нужно в тексте шаблона поменять наши спец команды на текст тайтла и текст контента. Полученную в результате страницу можно отправлять в браузер.
Реализуем описанное:
http.createServer(async (request, response) => { let lpath = 'layout.html'; let cpath = 'page' + request.url + 'content.html'; let tpath = 'page' + request.url + 'title.html'; let layout = await fs.promises.readFile(lpath, 'utf8'); let content = await fs.promises.readFile(cpath, 'utf8'); let title = await fs.promises.readFile(tpath, 'utf8'); layout = layout.replace(/\{% get content %\}/, content); layout = layout.replace(/\{% get title %\}/, title); response.writeHead(200, {'Content-Type': 'text/html'}); response.write(layout); response.end(); }).listen(3000);
Возьмите приведенный код и потестируйте его работу.
Реализуйте показ 404 страницы в случае, если URL не соответствует файлу. Пусть 404 страница хранится в файле page/404/title.html и page/404/content.html.
Добавьте к вашему коду работу с ресурсами.
Кроме тайтла и контента на страницах сайта может также изменяться мета-описание (погуглите). Реализуйте возможность добавлять его к страницам сайта.
Возьмите созданный вами в предыдущем уроке сайт из 6-ти страниц. Переделайте его в соответствии с данным уроком.
Реализуем статический сервер на NodeJS
Давайте теперь сделаем так, чтобы по URL на нашем сайте искался соответствующий ему HTML файл. Например, если запрашивается /page.html, то мы должны отдать такой же файл, а если запрашивается /dir/test.html, то мы должны отдать файл test.html из папки dir.
Давайте все наши HTML файлы разместим в папке root. Это будет корневая папка нашего сайта и поиск HTML файлов мы будем начинать относительно этой папки.
Реализуем описанное:
http.createServer(async (request, response) => { if (request.url != '/favicon.ico') { let path = 'root' + request.url'; // преобразуем URL в путь к файлу let text = await fs.promises.readFile(path, 'utf8'); response.writeHead(200, {'Content-Type': 'text/html'}); response.write(text); response.end(); } }).listen(3000);
Давайте теперь обработаем URL вида /dir/sub/. Как вы видите, в этом адресе не указано имя файла и его расширение. В интернете принято считать, что такой адрес ссылается на файл index.html, находящийся в этой папке. То есть наш URL следует трактовать как /dir/sub/index.html.
Давайте модифицируем наш код:
http.createServer(async (request, response) => { if (request.url != '/favicon.ico') { let path = 'root' + request.url'; if (await fs.promises.stat(path).isDirectory()) { path += 'index.html'; } let text = await fs.promises.readFile(path, 'utf8'); response.writeHead(200, {'Content-Type': 'text/html'}); response.write(text); response.end(); } }).listen(3000);
Если запрошенный URL не соответствует файлу на нашем сайте, очевидно, что мы должны отдавать 404 ошибку. Сделаем это:
http.createServer(async (request, response) => { if (request.url != '/favicon.ico') { let text; let status; let path = 'root' + request.url; if (await fs.promises.stat(path).isDirectory()) { path += '/index.html'; } try { status = 200; text = await fs.promises.readFile(path, 'utf8'); } catch (err) { status = 404; text = 'page not found'; } response.writeHead(status, {'Content-Type': 'text/html'}); response.write(text); response.end(); } }).listen(3000);
Рееализуйте описанный статический сервер.
Сделайте так, чтобы 404 страница тоже бралась из файла, например, из файла root/404.html.
При обращении к папке URL со слешем /dir/sub/ и без слеша считаются одинаковым /dir/sub и оба ведут на index папки. Проверьте, как наш сервер справляется с этим. Если есть какие-то проблемы - исправьте их.
Выдача ресурсов
Давайте теперь модифицируем наш сервер так, чтобы кроме HTML файлов, автоматически также выдавались запрошенные ресурсы.
Для начала давайте сделаем функцию, которая будет принимать путь к файлу и по расширению этого файла выдавать его mime тип:
function getMimeType(path) { let mimes = { html: 'text/html', jpeg: 'image/jpeg', jpg: 'image/jpeg', png: 'image/png', svg: 'image/svg+xml', json: 'application/json', js: 'text/javascript', css: 'text/css', ico: 'image/x-icon', }; let exts = Object.keys(mimes); let extReg = new RegExp('\\.(' + exts.join('|') + ')$'); let ext = path.match(extReg)[1]; if (ext) { return mimes[ext]; } else { return 'text/plain'; } }
Имея такую функцию, легко адаптировать наш сервер для выдачи файлов любого типа:
http.createServer(async (request, response) => { let text; let status; let path = 'root' + request.url; if (fs.promises.stat(path).isDirectory()) { path += '/index.html'; } try { status = 200; text = await fs.promises.readFile(path, 'utf8'); } catch (err) { status = 404; text = 'page not found'; } response.writeHead(status, {'Content-Type': getMimeType(path)}); // изменение response.write(text); response.end(); }).listen(3000);
Скопируйте функцию getMimeType из учебника. Исправьте код вашего сервера, используя эту функцию.
Сделайте сайт о вашем городе. Пусть сайт состоит из 6-ти HTML страниц. К этим страницам должен быть подключен общий CSS файл, общий JavaScript файл, добавлены картинки. На каждой странице должна быть менюшка, с помощью которой можно будет перемещаться по страницам сайта.
Убираем расширение из URL
Мы реализовали наш сервер так, что все URL (кроме адресов папок) заканчиваются расширением .html. Однако, в современном мире наличие расширения файла в адресе считается признаком дурного тона и старомодности.
Для красоты необходимо сделать так, чтобы адрес вида /page/ соответствовал файлу root/page.html, адрес вида /dir/page/ - файлу root/dir/page.html, а адрес главной страницы / - файлу root/index.html.
При этом запросы к ресурсам должны работать, как и работали.
Уберите из ваших адресов расширения HTML файлов.
Элементы в шаблоне сайта на NodeJS
В предыдущем уроке вы научились хранить макет сайта в одном файле шаблона. Как вы уже знаете, в этом файле остаются только общие части: хедер, сайдбары, футер. Как правило, это удобно, однако, HTML код этих частей может быть достаточно большим, что мешает восприятию общей структуры сайта.
Было бы удобно вынести эти части в отдельные файлы, так сказать "с глаз долой", чтобы обращаться к ним по мере необходимости, а в остальное время чтобы они не мешали чтению другого кода.
Давайте придумаем для этого специальную команду {% get element '' %}, где в кавычках будет писаться имя элемента. В этом случае наш файл шаблона превратится в нечто такое:
<!DOCTYPE html>
<html>
<head>
<title>{% get title %}</title>
</head>
<body>
<div id="wrapper">
<header> {% get element 'header' %} </header>
<main> {% get content %} </main>
<aside> {% get element 'aside' %} </aside>
<footer> {% get element 'footer' %} </footer>
</div>
</body>
</html>
Давайте будем хранить элементы макета в папке elems. По запросу пользователя нам нужно будет прочитать файл с шаблоном сайта, затем найти все места-команды на вставку элементов и заменить их на тексты соответствующих файлов.
Давайте напишем код, реализующий описанное, для краткости опустив работу с тайтлом и контентом, а также ресурсами сайта:
http.createServer(async (request, response) => { let lpath = 'layout.html'; let layout = await fs.promises.readFile(lpath, 'utf8'); let reg = /\{% get element '(.+?)' %\}/g; layout = layout.replace(reg, async (match0, match1) => { return await fs.promises.readFile('elems/' + match1 + '.html', 'utf8'); }); response.writeHead(200, {'Content-Type': 'text/html'}); response.write(layout); response.end(); }).listen(3000);
Разберите приведенный мною код. Напишите текст, в котором вы объясните работу этого кода.
Добавьте этот код к коду сервера, созданному вами в предыдущем уроке.
Возьмите созданный вами сайт из 6-ти страниц и разделите шаблон сайта на элементы.
Функции в JavaScript. Классический способ создания
Что такое функция
Function Declaration;
Function Expression;
Arrow Function;
Объявление и вызов функции
-
объявление (создание) функции;
-
вызов (выполнение) этой функции.
function имя (параметры) {
// код функции
}
// объявление функции someName
function someName() {
alert('Вы вызвали функцию someName!');
}
// function - ключевое слово, которое означает что мы хотим создать функцию
// someName - имя функции
// () - круглые скобки внутри которых при необходимости мы можем поместить параметры через запятую
// { ... } - код или тело функции


Параметры и аргументы функции
// объявление функции sayWelcome (userFirstName и userLastName - это параметры)
// userFirstName - будет принимать значения 1 аргумента, а userLastName соответственно значение 2 аргумента
function sayWelcome (userFirstName, userLastName) {
console.log('Добро пожаловать, ' + userLastName + ' ' + userFirstName);
}
// вызов функции sayWelcome ('Иван' и 'Иванов' - это аргументы)
sayWelcome('Иван', 'Иванов');
// ещё один вызов функции sayWelcome ('Петр', 'Петров' - это аргументы)
sayWelcome('Петр', 'Петров');
// вызов функции sayWelcome с одним аргументом 'Петр'
sayWelcome('Петр'); // Добро пожаловать, undefined Петр
// вызов функции sayWelcome без аргументов
sayWelcome(); // Добро пожаловать, undefined undefined
function f() {
// проверим является ли arguments обычным массивом
console.log(Array.isArray(arguments));
}
f(); // false
function sum() {
const num1 = arguments[0] // получаем значение 1 аргумента
const num2 = arguments[1] // получаем значение 2 аргумента
console.log(num1 + num2);
}
sum(7, 4); // 11
function sum() {
let sum = 0;
// arguments.length - число аргументов
for (let i = 0, length = arguments.length; i < length; i++) {
if (typeof arguments[i] === 'number') {
sum += arguments[i];
}
}
console.log(sum);
}
sum(4, 20, 17, -6); // 35
sum('', 3, -5, 32, null); // 30
function sum() {
let sum = 0;
for (argument of arguments) {
if (typeof argument === 'number') {
sum += argument;
}
}
console.log(sum);
}
function names() {
// превращаем arguments в полноценный массив
var argsArr = Array.from(arguments);
console.log(argsArr);
}
names('Даша', 'Маша', 'Нина'); // ["Даша", "Маша", "Нина"]
function fa(a, b) {
const c = 4;
function fb(d) {
console.log(a + b + c + d);
}
fb(1);
}
fa(3, 5); // 13
fa(4, 7); // 16
// параметр a не доступен за пределами функции fa
console.log(a); // Uncaught ReferenceError: a is not defined
const a = 10;
function fa(b) {
console.log(a + b);
}
fa(7); // 17
Передачи одной функции в другую. Колбэки
// функция, которая будет выводить в консоль то, что ей передали в виде аргумента
function outputResult(result) {
console.log(result);
}
// функция, которая принимает на вход 2 аргумента и колбэк
function sum(num1, num2, callback) {
// вычислим сумму 2 значений и сохраним его в result
const result = num1 + num2;
// вызовем колбэк
callback(result);
}
sum (5, 11, outputResult);
function myfunc() {};
console.log(typeof myfunc); // function
function sum(num1, num2, callback) {
const result = num1 + num2;
if (typeof callback === 'function') {
callback(result);
}
}
Возвращаемое значение (return)
function f() {
return выражение;
}
function sum(a, b) {
const result = a + b;
}
// вызовем функцию и сохраним её результат в константу result
const result = sum(4, 3);
// выведем результат функции sum(4, 3) в консоль
console.log(result); // undefined
function sum(a, b) {
// вернём в качестве результата сумму a + b
return a + b;
}
// вызовем функцию и сохраним её результат в константу result
const result = sum(4, 3);
// выведем результат функции sum(4, 3) в консоль
console.log(result); // 7
function sum(a, b) {
// вернём в качестве результата сумму a + b
return a + b;
// код, расположенный после return никогда не выполняется
console.log('Это сообщение не будет выведено в консоль');
}
sum(4, 90);
Функция, которая возвращает функцию
function outer(a) {
return function(b) {
return a * b;
}
}
// в one будет находиться функция, которую возвращает outer(3)
const one = outer(3);
// в two будет находиться функция, которую возвращает outer(4)
const two = outer(4);
// выведем в консоль результат вызова функции one(5)
console.log(one(5)); // 15
// выведем в консоль результат вызова функции two(5)
console.log(two(5)); // 20
// выведем в консоль результат вызова функции one(5)
console.log(outer(3)(5)); // 15
// выведем в консоль результат вызова функции two(5)
console.log(outer(4)(5)); // 20
function outer(a) {
function inner(b) {
return a * b;
}
return inner;
}
function fa() {
return 'Привет!';
}
function fb() {
return fa;
}
fb()(); // Привет!
Рекурсия
function fact(n) {
// условие выхода из рекурсии
if (n === 1) {
return 1;
}
// возвращаем вызов функции fact(n - 1) умноженное на n
return fact(n - 1) * n;
}
console.log(fact(5)); // 120
function counter(value) {
// условие выхода из рекурсии
if (value < 10) {
console.log(value);
// возвращаем вызов функции counter(value + 1)
return counter(value + 1);
}
}
counter(1); // 1, 2, 3, 4, 5, 6, 7, 8, 9
counter(7); // 7, 8, 9
Перегрузка функций в JavaScript
// объявление функции bodyBgColor
function bodyBgColor(color) {
// если параметр color имеет в качестве значения строку, то установим цвет фона body
if (typeof color === 'string') {
document.body.style.backgroundColor = color;
}
// вернём в качестве результата текущий цвет фона body
return getComputedStyle(document.body).backgroundColor;
}
// получим текущий цвет body и выведем его в консоль
console.log(bodyBgColor());
// установим новый цвет фона body
bodyBgColor('green');
function calculateСalories(gender, height) {
let result = gender === 'man' ? (height - 100) * 20 : (height - 105) * 19;
if (typeof arguments[2] === 'number') {
result *= arguments[2];
}
return result.toFixed(0);
}
console.log(`Оптимальное кол-во ккал: ${calculateСalories('man', 185)}`);
console.log(`Оптимальное кол-во ккал: ${calculateСalories('woman', 168, 1.2)}`);
console.log(`Оптимальное кол-во ккал: ${calculateСalories('woman', 168)}`);
Новые возможности, которые появились в ES6+ для функций
Значение параметра по умолчанию
function bodyBgColor(color = '#009688') {
document.body.style.backgroundColor = color;
}
// при вызове функции без указания аргументов цвет фона body будет установлен, равным '#009688'
setBGColor();
// при указании аргумента цвет фона body будет установлен в соответствии с его значением
setBGColor('red'); // цвет фона будет равен red
function setBGColor(color) {
// устанавливаем color значение по умолчанию '#009688', если он имеет тип undefined
color = typeof color === 'undefined' ? '#009688' : color;
// устанавливаем цвет фона body равным значению color
document.body.style.backgroundColor = color;
}
Оставшиеся параметры (rest parameters)
// ...nums - оставшиеся параметры, к которым можно обратиться в данном случае по nums
function calculate(action, ...nums) {
let result = 0;
nums.forEach(function(num) {
switch (action) {
case 'sum':
result += num;
break;
case 'multiply':
result *= num;
break;
}
});
return result;
}
console.log(calculate('sum', 3, 4, 21, -4)); // 24
console.log(calculate('multiply', 1, 4, 3)); // 12
Что такое встроенные (стандартные) функции
// вызов функции alert
alert("Некоторый текст");

// 1. функция, не возвращающая никакого результата
function sayWelcome (userFirstName, userLastName) {
console.log("Добро пожаловать, " + userLastName + " " + userFirstName);
}
// попробуем получить результат у функции, которая ничего не возвращает
console.log(sayWelcome ("Иван", "Иванов"));
// 2. функция, содержащая оператор return без значения
function sayDay (day) {
day = "Сегодня, " + day;
return;
//эта инструкция не выполнится, т.к. она идёт после оператора return
console.log(day);
}
// попробуем получить результат у функции, которая содержит оператор return без значения
console.log(sayDay("21 февраля 2016г."));
