Функции

Передача аргументов по ссылке

В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.

Однако можно организовать изменение локальной переменной одной функции с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения?! Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение. Рассмотрим пример:

#include <stdio.h>
 
void multi (int *px, int y);
 
int main () {
    int x = 34, y = 6;
 
    multi(&x, 367);
    multi(&y, 91);
    printf("%d %d\n", x, y);
}
 
void multi (int *base, int pow) {
    while (pow >= 10) {
        *base = *base * 10;
        pow = pow  10;
    }
}

Функция ничего не возвращает, что подчеркнуто с помощью ключевого слова . Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Но по сути это адрес переменной x из фукнции , а значит меняется и ее значение.

Когда вызывается в , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов привел бы к ошибке, а вызов — правильный, т.к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в указатель и передавать именно его (в данном случае сама переменная p содержит адрес):

int x = 34, y = 6;
int *p;
 
p = &x;
multi(p, 367);
p = &y;
multi(p, 367);
printf("%d %d\n", x, y);

Кроме того, следует знать, что функция может возвращать адрес.

  1. Перепишите код первого примера этого урока так, чтобы в нем использовался указатель; а код примера с функцией , наоборот, избавьте от указателей.
  2. Напишите программу, в которой помимо функции были бы еще две функции: в одной вычислялся факториал переданного числа, в другой — находился n-ый элемент ряда Фибоначчи (n — параметр функции). Вызовите эти функции с разными аргументами.
  3. Придумайте и напишите программу, в которой из функции вызывается другая функция, а из последней вызывается еще одна функция.

Как это работает?

Компьютеры не понимают JavaScript — браузеры понимают.

Помимо обработки сетевых запросов, прослушивания кликов мыши и интерпретации HTML и CSS для рисования пикселей на экране, в браузер встроен движок JavaScript.

Движок JavaScript — это программа, написанная, скажем, на C ++, которая обрабатывает весь код JavaScript, символ за символом, и «превращает» его в нечто, что ЦП компьютера может понять и выполнить — то есть в машинный код.

Это происходит синхронно, то есть по одной строке за раз и по порядку.

Он делают это потому, что машинный код сложен, и потому что инструкции машинного кода у разных производителей процессоров разные.

Таким образом, движок делают всю эту работу за разработчиков JavaScript, иначе веб-разработка была бы намного сложнее, менее популярной, и у нас не было бы таких вещей, как Medium (оригинальная статья размещена на Medium), где мы могли бы писать статьи, подобные этой (и я бы сейчас спал) ,

Движки JavaScript отличаются по типу выполнения. Он может пройти по каждой строке JavaScript и выполнять их по строчно (см. Интерпретатор), или он может быть умнее и обнаруживать такие вещи, как функции, которые часто вызываются и всегда дают один и тот же результат. Затем он может скомпилировать их в машинный код только один раз, чтобы в следующий раз, когда он встретит его, он выполнял уже скомпилированный код, что намного быстрее (см. Компиляция Just-in-time). Или он может заранее скомпилировать весь код в машинный код (см. Compiler).

Сейчас самый распространенный V8 — это такой движок JavaScript, который Google был создан в 2008 году. В 2009 году у парня по имени Райан Даль появилась идея использовать V8 для создания Node.js, среды выполнения для JavaScript вне браузера, что означало, что этот язык может также использоваться для серверных приложений.

Параметры функции

В файле greet.js вы создали базовую функцию, которая выводит в консоль приветствие. С помощью параметров можно расширить действия функции и сделать код более гибким. Параметры – это входные данные, которые передаются в функции как имена и ведут себя как локальные переменные.

К примеру, когда пользователи входят в приложение, программа может приветствовать их по имени.

Добавьте в функцию параметр name, чтобы объявить имя приветствуемого пользователя.

Теперь функция greet имеет один параметр внутри круглых скобок. Имена параметров следуют тем же правилам, что и имена переменных. Теперь внутри функции вместо статической строки «Hello, World» есть строка шаблона, содержащая параметр, который ведет себя как локальная переменная.

Как вы могли заметить, значение параметра name не присваивается в коде, это делается при вызове функции. При вызове функции имя пользователя передается в качестве аргумента. Аргумент – это фактическое значение, которое передается в функцию (в данном случае это имя пользователя, например 8host).

Значение 8host передается в функцию через параметр name. Теперь параметр name будет представлять в данной функции это значение. Код файла greetUser.js выглядит так:

Запустив эту программу, вы получите такой вывод:

Теперь вы знаете, как можно повторно использовать функцию.

Кроме параметров, внутри функций можно объявлять переменные. Эти переменные называются локальными и существуют только внутри их блока функции. Область переменных определяет доступность переменных; переменные, которые определены внутри функции, недоступны извне функции, но их можно использовать столько раз, сколько используется в программе функция, которой они принадлежат.

Более сложные примеры замыканий

Использование тройного замыкания и «приватная» функция

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },

    decrement: function() {
      changeBy(-1);
    },

    value: function() {
      return privateCounter;
    }
  }
};

var counter = makeCounter();

alert(counter.value());  // 0.

counter.increment();
counter.increment();
alert(counter.value()); // 2.

counter.decrement();
alert(counter.value()); // 1.


В этом примере можно наблюдать интересный эффект: попытка выполнить  приводит к ошибке.

Выражаясь языком ООП, функция является как бы приватной (private). Это значит, что к ней нельзя получить доступ за пределами ее внешней функции .

А три функции , и наоборот являются как бы публичными (public). И более того: они имеют общее лексическое окружение, заключенное в блоке после ключевого слова return. Да, и так тоже можно!

Обработка Ajax-запроса

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

Рассмотрим пример:

function sendAjax(){ 
    var outerVar = "abc"; 
    $.ajax({ 
       cache : ..., 
       url : ..., 
       type : ..., 
       crossDomain: ..., 
       data : ...., 
       contentType : ..., 
       timeout : 20000, 
       dataType : ..., 
       success : function(response){ 
            console.log(outerVar);	 
        }, 
       error : function(jXhr, err){ 
            console.log(outerVar);	 
        }, 
       xhrFields: { 
           withCredentials: true 
       } 
  }); 
} 

Здесь — та самая внешняя функция. Внутри нее определена функция, которая запускается, если процесс отправки Ajax-запроса завершился успешно (success). Эта callback-функция использует внешнюю переменную outerVar:

Что и требовалось доказать.

Работа с DOM

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

Допустим, нужно устанавливать новый размер шрифта по нажатию кнопок, расположенных на HTML-странице:

Работа с DOM

Напишем функцию , которая будет реагировать на соответствующие события для каждой кнопки:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

Внутри нее реализуем замыкание. В данном случае это анонимная функция, которая для изменения размера шрифта использует внешнюю переменную . И будем возвращать эту анонимную функцию как результат работы внешней функции :

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

Тогда , и — это отдельные функции, которые устанавливают размер шрифта страницы равным 12, 14 и 16 пикселей соответственно.

Локальные переменные функции

В функцию могут входить и локальные переменные (объявляются через var). Они видны только внутри функции:

function showMessage() {
  var message = 'Привет, моё имя — Петя!'; // это локальная переменная
  alert( message );
}
showMessage(); // 'Привет, моё имя — Петя!'
alert( message ); // <-- здесь ошибка, ведь переменная видна только внутри функции

Помните, что блоки while, switch, for, if/else, do..while никак не влияют на зону видимости переменных, то есть при объявлении переменных в данных блоках они будут видны во всей функции. Пример:

       function count() {
  // переменные i и j не будут удалены по окончании цикла
  for (var i = ; i < 3; i++) {
    var j = i * 2;
  }
  alert( i ); // i=3, последнее значение i, цикл при нём перестал работать
  alert( j ); // j=4, последнее значение j, которое цикл вычислил
}

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

function count() {
  var i, j; // здесь мы передвинули объявление var в начало
  for (i = ; i < 3; i++) {
    j = i * 2;
  }
  alert( i ); // i=3
  alert( j ); // j=4
}

Объект arguments

В JavaScript можно создавать функции с произвольным числом аргументов. Доступ ко всем указанным при вызове функции аргументам, в том числе к лишним аргументам, которым не были назначены параметры, осуществляет­ся через объект arguments, который доступен только внутри тела функции.

Свойство этого объекта содержит число переданных аргументов.

На­пишем функцию, вычисляющую сумму произвольного числа аргумен­тов:

Выполнить код »
Скрыть результаты

Объект является массивоподобным объектом, который позво­ляет передать функции значения аргументов, извлекаемые не по имени, а по номеру.

Поскольку он похож на массив, обратиться к переданным функции аргументам можно так же, как и к элементам массива: первый аргумент будет доступен в теле функ­ции как элемент массива arguments,

второй аргумент

arguments

Функция, приводимая в следующем примере, возвращает строку, содержащую значения параметров (переданные аргументы) и их общее количество, которые были указаны в вызове функции (не в ее определении):

Выполнить код »
Скрыть результаты

Зачем они нужны, если есть обычные?

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

Но если вы уже прокачались и изучаете или пробуете писать сложные проекты, где нужно обращаться к методу this внутри функции, используя свойства внешнего объекта, — то да, лучше использовать стрелочные функции.

Вывод простой: если вы задаёте себе этот вопрос — вам пока не нужны стрелочные функции. Но знать о них стоит уже сейчас.

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Олег Вешкурцев

Основы бэкенд

Как уже упоминалось, еще 10 лет назад JS использовался только для фронтенд-разработки. Теперь, благодаря Node.js, JavaScript работает и на серверной стороне.

SSR, CSR, изоморфные приложения

SSR — Server-Side Rendering – формирование страницы на стороне сервера.

CSR — Client Side Rendering – формирование страницы на стороне клиента (в браузере).

Какие проблемы имеются у этих способов?

SSR имеет ряд ограничений (скорость интернета у клиента, количество одновременных пользователей и т.д.). Это заметно снижало скорость, но это годилось для SEO оптимизации (так как при запросе к серверу робот получал сформированную страницу).

CSR – избавился от проблем скорости SSR (быстрый рендеринг, кеширование), появились одностраничные Single Page Application (SPA).  Но для таких одностраничных приложений CSR SEO оптимизация не годилась, так как весь контент передается клиенту и формируется в браузере (после инициализации начинается загрузка контента), а поисковый робот при запросе на сервер получает лишь пустую страницу.

Node.js

Node — это среда для выполнения JS на стороне сервера. Вам не нужно будет изучать новый синтаксис, а только лишь научиться импортировать и экспортировать файлы, разбивать код на модули и использовать менеджер пакетов npm.

Теперь с помощью NodeJS стало возможным написать логику на серверной стороне, и она будет работать и на сервере (при первом обращении посетителя или поискового робота генерировался HTML с контентом страницы) и в браузере (последующие переходы посетителя). Это и называется изоморфное, универсальное приложение.

Схема функционирования простая: при первом заходе посетитель отправляет запрос на сервер NodeJS, который обращается к API-серверу, берёт данные в виде JSON и формирует страницу HTML, возвращая её клиенту. Теперь приложение работает на клиентской стороне, в браузере: при переходе на другие страницы приложение обращается за данными к API-серверу, и отрисовывает страницу уже в браузере.

В React (см. дальше) реализация этой схемы осуществляется разными и сложными путями. В качестве готовых решений есть для этого, например, фреймворк Next.js. В документации Vue (см. дальше) есть целый раздел, посвященный SSR. Там указан фреймворк Nuxt — Vue + SSR, при помощи которого можно быстро создавать универсальные приложения.

Серверы, HTTP, Express.js

Изучив Node, можно более глубже изучить бэкенд-разработку и разобраться в серверах и маршрутизации. Акцент делать на портах, протоколах HTTP. Затем можно просмотреть  Express-Node-библиотеку для обработки запросов.

Асинхронный JavaScript

Асинхронность является одной из главных фишек (фич) JavaScript, хотя и создаёт некий раскол среди разработчиков: некоторые её любят, а некоторые — ненавидят. Нужно разобраться в её  плюсах и минусах. Начните со стека вызовов, цикла событий и коллбеков, а затем перейдите к изучению промисов здесь или здесь.

Базы данных, СУБД, схемы, модели и ORM

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

Нужно научиться различать реляционные и нереляционные базы данных и разобраться в типах связей. Затем изучить SQL и быть в курсе разных систем управления базами данных. Знание ORM тоже не помешает.

Веб-сокеты

Не стоит пренебрегать этой темой. Веб-сокеты очень полезны. В отличие от протокола HTTP WebSocket позволяет работать с двунаправленным потоком данных. Самой распространённой реализацией является библиотека socket.io .

Также нужно разобраться в механизме взаимодействия приложения с пользователем, обработки им входа в учетную запись, отслеживания личности при помощи cookies — небольших текстовых файлов, которые передаются от сервера браузеру по HTTP-запросу. Связь между БД и страницей авторизации использует библиотеку express-session.

Я знал ответ, это же просто

Если на опрос выше вы ответили, что функция myFunc является анонимной, поздравляю—это правильно! Отметьте этот день красным цветом в календаре, позовите родных и близких, начинайте разливать шампанское по бокалам.

Итак, значит функция в коде блока выше является анонимной:

const myFunc = function() { };

А что, если я тебе скажу, что ты можешь обратится к свойству name, и получить конкретное значение?

myFunc.name // "myFunc"

Полученное значение не пустое, но при этом функция выше, как мы выяснили, анонимная, но это же какой-то понятийный коллапс, товарищи! Не торопитесь уходить в backend-разработку, Шерлок Холмс уже начал свое расследование.

Возврат строки, числа или другого типа данных

function renderGreeeting (name) { // Получаем элемент для рендеринга сообщения приветствия &nbsp; &nbsp;
    let app = document.querySelector('#app'); // Создаем приветствие
    let p = document.createElement('p'); // Создаем тег p
    p.textContent = `Hi, ${name}! How are you today?`; // Вставляем текст в тег p
    app.appendChild(p); // Вставляем тег p в UI
}

Представим, что мы используем какое-то имя (name), введенное пользователем, для создания некоторой HTML-разметки в пользовательском интерфейсе.

В этом примере можно было бы поступить следующим образом. Сначала создать собственную функцию и вписать внутрь эти строки. После этого можно использовать return, который вернет элемент p в переменной. Это позволит использовать ее в коде в дальнейшем.

function getGreeting (name) {      
    let p = document.createElement('p');      
    p.textContent = `Hi, ${name}! How are you today?`;      
    return p; 
} 

function renderGreeeting (name) {    
    let app = document.querySelector('#app'); // Получаем элемент для рендеринга приветствия 
    app.appendChild(getGreeting(name)); // Вставляем элемент в UI  
}

Конечно, это достаточно простой и даже глупый пример, потому что return в JavaScript функции возвращает всего лишь один абзац. Но по мере увеличения объема кода растет и полезность этого подхода.

function getGreeting (name) { // Создаем элементы    
    let wrap = document.createElement('div');    
    let h1 = document.createElement('h1');    
    let p = document.createElement('p'); // Добавляем содержимое    
    h1.textContent = `Hi, ${name}!`;    
    p.textContent = 'How are you today?';    
    wrap.appendChild(h1);    
    wrap.appendChild(p);    
    return wrap; 
}

Что такое this?

указывает на объект, который выполняет текущий кусок JavaScript-кода.

Другими словами, – это ссылка на текущий контекст выполнения. У каждой функции есть этот контекст. Он указывает, где и как эта функция вызывается.

В случае с контекстом выполнения не имеет никакого значения, где и когда функция была объявлена.

В отличие от замыканий (еще одной базовой концепции языка)

Им как раз очень важно, где была объявлена функция

Если интересно, загляните сюда:

  • Пора понять замыкания в JavaScript! Часть 1. Готовим фундамент
  • Пора понять замыкания в JavaScript! Часть 2. Переходим к делу

Пример:

Функция обращается к объекту , пытаясь получить его свойство и вывести его в консоль. Другими словами, она пытается получить свойство текущего контекста выполнения.

Если вызвать функцию просто как функцию (), ее контекстом выполнения будет глобальный контекст ( в браузере). Тогда обращение будет равносильно , что в данном коде равно , так как переменные объявленные через записываются в свойства глобального объекта.

Внимание! Внимание!

В строгом режиме ( — который всегда нужно использовать!) функции вызываемые обычным способом не имеют контекста выполнения (). Поэтому в коде будет ошибка, так как функция не может получить поле объекта, которого не существует.

Если же вызвать функцию как метод объекта (через точку или квадратные скобки — ), то контекстом выполнения этой функции станет сам объект . Аналогично при вызове .

В этом случае ссылается на объекты и соответственно, и поле запрашивает у них.

Вот и все, что нужно знать о контексте выполнения. Ничего сложного.

Стек исполнения (Execution Stack)

Мы знаем, что контекст выполнения функции создается каждый раз, когда вызывается функция.

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

Посмотрите на следующий пример:

function a() {
  // some code
}

function b() {
  // some code
}

a();
b();

Когда происходит вызов функции a(), создается контекст выполнения функции, как описано выше, и выполняется код внутри функции.

Когда выполнение кода завершено (оператор return или скобка } ), контекст выполнения функции для функции a() уничтожается.

Затем происходит вызов b(), и тот же процесс повторяется для функции b().

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

function a() {
  // some code
  b();
  // some more code
}

function b() {
  // some code
}

a();

В этом случае создается контекст выполнения функции для a(), и прямо в середине выполнения a() встречается вызов b(). Абсолютно новый контекст выполнения функции создается и для b(), но без разрушения контекста выполнения a(), так как его код еще не полностью выполнился.

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

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

Как только он завершит выполнение, он будет извлечен из стека, далее возобновится выполнение следующего контекста выполнения и так далее, пока стек выполнения не станет пустым.

Этот стек называется стеком выполнения (execution stack), представленным на рисунке ниже.

JavaScript execution stack

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

Идём в обход: динамическая область видимости против статической области видимости

У динамических языков программирования существует стековая архитектура — stack-based implementations, локальные переменные и функции хранятся в стеке. Поэтому, во время выполнения стека, программа определяет какую переменную вы имеете в виду. С другой стороны, статическая область видимости — это когда переменные ссылаются на контекст и фиксируются на момент создания. Другими словами, структура исходного кода программы определяет к каким переменным вы обращаетесь.

Вы могли задаваться вопросом, как различаются динамическая и статическая области видимости. Вот два примера, которые помогут это продемонстрировать:

Пример 1

Как мы видим из примера выше, статическая и динамическая область видимости возвращают разные значения при вызове функции bar.

В статической области видимости возврат значения bar зависит от значения x. Это происходит из-за того, что статическая и лексическая структура исходного кода приводит x и к 10, и к 15.

Динамическая область видимости даёт нам стек определённых переменных, которые отслеживаются во время выполнения. Поэтому x, которую мы используем, зависит от того, что находится в её области видимости и как она была динамично определена во время выполнения. Выполнение функции bar выталкивает на верхушку стека, заставляя foo вернуть 7.

Пример 2

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

Как вы можете заметить, динамическая область видимости часто создаёт некоторую двусмысленность. Она не даёт точно понять, какая свободная переменная будет передана.

Локальные переменные функции

В функцию могут входить и локальные переменные (объявляются через var). Они видны только внутри функции:

function showMessage() {
  var message = 'Привет, моё имя — Петя!'; // это локальная переменная
  alert( message );
}
showMessage(); // 'Привет, моё имя — Петя!'
alert( message ); // <-- здесь ошибка, ведь переменная видна только внутри функции

Помните, что блоки while, switch, for, if/else, do..while никак не влияют на зону видимости переменных, то есть при объявлении переменных в данных блоках они будут видны во всей функции. Пример:

       function count() {
  // переменные i и j не будут удалены по окончании цикла
  for (var i = ; i < 3; i++) {
    var j = i * 2;
  }
  alert( i ); // i=3, последнее значение i, цикл при нём перестал работать
  alert( j ); // j=4, последнее значение j, которое цикл вычислил
}

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

function count() {
  var i, j; // здесь мы передвинули объявление var в начало
  for (i = ; i < 3; i++) {
    j = i * 2;
  }
  alert( i ); // i=3
  alert( j ); // j=4
}

Функции-генераторы в «ES6»

Еще одно новое определение функции из стандарта «ES6» — функция-генератор. Она способна остановить и продолжить выполнение.

function *function_name(){}

Функция-генератор создаёт итератор (повторитель). Метод итератора «next» используется для выполнения кода внутри функции-генератора до тех пор, пока не будет достигнуто ключевое слово «yield». Затем значение после слова «yield» возвращается и выполнение приостанавливается.

Если ключевое слово «yield» не достигается, то происходит возврат значения «undefined» и выполнение итератора дальше не происходит.

function *randomIncrement(i) {
 yield i + 3;
 yield i + 5;
 yield i + 10;
 yield i + 6;
}
var itr = randomIncrement(4);
console.log(itr.next().value); //7
console.log(itr.next().value); //9
console.log(itr.next().value); //14

Функции-генераторы полезны если требуется перебрать значения один за другим в разных точках кода, приостанавливая перебор.

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: