Циклы

Цикл for…of()

В отличие от цикл for…of() предназначен для вывода значений элементов массива. Синтаксис таков:

Синаксис цикла for … of

JavaScript

for(let value of arr){
//некий код
}

1
2
3

for(let value of arr){

//некий код

}

Пример использования цикла на основе уже известного нам массива. Перепишем код таким образом:

Использование цикла for..of

JavaScript

let words = ;
for(let one of words){
console.log(one);
}

1
2
3
4

let words=’Уж’,’я’,’веники’,’не’,’вяжу’;

for(let one of words){

console.log(one);

}

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

Стоит отметить, что цикл появился в стандарте ES6 (EcmaScript2015) и может не работать в Internet Explorer и некоторых устаревших версиях браузеров. Смотрите поддержку этого цикла на .

Примечание: учтите, что цикл проводит перебор значений итерируемых конструкций (Array, Map, Set и DOM) и в переменную-счетчик отправляется значение, по которому происходит итерирование. Однако, из-за того, что в объектах или ассоциативных массивах  нет итерируемых значений, а есть ключи или названия свойств, цикл не может использоваться для перебора объектов и ассоциативных массивов, как и цикл .

Просмотров:
441

Flowchart of while Loop

Flowchart of JavaScript while loop

Example 1: Display Numbers from 1 to 5

Output

1
2
3
4
5

Here is how this program works.

Iteration Variable Condition: i <= n Action
1st 1 is printed. i is increased to 2.
2nd 2 is printed. i is increased to 3.
3rd 3 is printed. i is increased to 4.
4th 4 is printed. i is increased to 5.
5th 5 is printed. i is increased to 6.
6th The loop is terminated

Example 2: Sum of Positive Numbers Only

Output

Enter a number: 2
Enter a number: 5
Enter a number: 7
Enter a number: 0
Enter a number: -3
The sum is 14.

In the above program, the user is prompted to enter a number.

Here, is used because takes input from the user as a string. And when numeric strings are added, it behaves as a string. For example, . So converts a numeric string to number.

The loop continues until the user enters a negative number. During each iteration, the number entered by the user is added to the variable.

When the user enters a negative number, the loop terminates. Finally, the total sum is displayed.

Цикл while

Синтаксис цикла while следующий:

Сначала объявляем переменную i, которая является счётчиком и внутри цикла мы этот счётчик инкрементируем. Внутри круглых скобок пишем условие входа/выхода из цикла.

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

Для примера выведем строку ‘Всем привет!’ 10 раз.

var i = 0;
while(i ');
    i++;
}

Переменная i, может начаться как с 0 так и с 1 или с другого любого числа.

Условие выхода является в тоже время и условием входа. Цикл работает следующим образом: Сначала проверяется если переменная i, меньше 10, и если условие истина, то мы входим в цикл, иначе, нет. В данном случае если переменная i будет равна 30, например, то цикл не выполнится, потому что 30 не меньше 10.

Зашли цикл, вывели строчку ‘Всем привет’, инкрементировали счётчик и опять переходим к условию, где опять проверяем если значение переменной i, меньше 10, то мы входим в цикл, иначе выходим из него. И так происходит до того момента когда условие входа станет лож, то есть значение переменной i будет 10. 10 не меньше 10, поэтому мы уже не входим в цикл, а идём дальше.

Замечание! Не забудьте инкрементировать счётчик (i++), иначе опять же получится бесконечный цикл.

С циклом while разобрались, теперь перейдём к циклу do-while.

Использование for без блоков

Все три выражения в заголовке цикла for являются необязательными.

Например, в блоке инициализации не требуется инициализировать переменные. Цикл в примере ниже полностью идентичен приведённому выше:

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

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

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

Можно опустить все три блока. Для избежания зацикливания используйте break для завершения цикла, а также изменяйте (увеличивайте или уменьшайте) переменную-счётчик, так чтобы условие для оператора break в какой-то момент сработало:

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

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

Практическое упражнение: Заполнение списка гостей

Возьмите список имен, хранящихся в массиве, и поместите их в список гостей. Тут не всё так просто: мы не хотим приглашать Фила и Лолу, потому что они наглые и всё сожрут! У нас есть два списка. Один для тех, кого мы хотим пригласить, второй для тех, кого приглашать не нужно.

Для этого нужно сделать следующее:

  • Напишите цикл, который будет повторяться от 0 до длины массива .
    Вам нужно начать с инициализатора , но какое нужно использовать условие выхода?
  • Во время каждой итерации цикла нужно проверять, соответствует ли текущий элемент массива именам «Фил» или «Лола», используя условный оператор:
    • Если это так, привяжите этот элемент массива в конец  абзаца , за которым следуют запятая и пробел. 
    • Если это не так, привяжите этот элемент массива в конец  абзаца , за которым следуют запятая и пробел.

Мы уже предоставили вам:

  • — Ваш инициализатор.
  • — начало строки, которая объединит что-то до конца .
  • — начало строки, которая объединит что-то до конца .

Дополнительный бонусный вопрос — после успешного выполнения вышеупомянутых задач у вас останется два списка имен, разделенных запятыми, но они будут составлены некорректно: в конце каждого списка будет запятая. Сможете ли вы составить эти списки так, чтобы в конце каждой строки вместо запятой была точка. За помощью можно обратиться к статье «Полезные строковые методы».

Если вы допустили ошибку, вы всегда можете сбросить пример с помощью кнопки «Сброс» (Reset). Если у вас совсем ничего не получается, нажмите «Показать решение», чтобы увидеть решение.

Цикл while

Оператор if является базовым условным оператором в языке JavaScript, а базовым циклом для JavaScript можно считать цикл while. Он имеет следующий синтаксис:

while (выражение)
{
    инструкция
}

Цикл while начинает работу с вычисления выражения. Если это выражение имеет ложное значение, интерпретатор пропускает инструкцию, составляющую тело цикла, и переходит к следующей инструкции в программе. Если выражение имеет истинное значение, то выполняется инструкция, образующая тело цикла, затем управление передается в начало цикла и выражение вычисляется снова. Иными словами, интерпретатор снова и снова выполняет инструкцию тела цикла, пока значение выражения остается истинным

Обратите внимание, что имеется возможность организовать бесконечный цикл с помощью синтаксиса while(true)

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

Кроме того, если изменяемая переменная (или переменные) присутствует в выражении, значение выражения может меняться при каждом проходе цикла

Это важно, т.к. в противном случае выражение, значение которого было истинным, никогда не изменится и цикл никогда не завершится! Ниже приводится пример цикла while, который выводит числа от 0 до 9:

Как видите, в начале переменной count присваивается значение 0, а затем ее значение увеличивается каждый раз, когда выполняется тело цикла. После того как цикл будет выполнен 10 раз, выражение вернет false (т.е. переменная count уже не меньше 10), инструкция while завершится и интерпретатор перейдет к следующей инструкции в программе. Большинство циклов имеют переменные-счетчики, аналогичные count. Чаще всего в качестве счетчиков цикла выступают переменные с именами i, j и k, хотя для того чтобы сделать программный код более понятным, следует давать счетчикам более наглядные имена.

Как сделать Object и простые объекты итерируемыми

Простые объекты не являются итерируемыми, как и объекты из .

Однако этот момент можно обойти, добавив @@iterator к Object.prototype с пользовательским итератором.

Переменная  содержит свойства объекта, полученного с помощью вызова . В функции next возвращается каждое значение из переменной properties и обновляется count, чтобы получить следующее значение из переменной properties, используя переменную count в качестве индекса. Когда count будет равен длине properties, устанавливаем значение true, чтобы остановить итерацию.

Тестирование с помощью Object:

Работает!!!

С простыми объектами:

Та-дам!! 

Стоить добавить этот способ в качестве полифилла, чтобы использовать for..of с любыми объектами в приложении.

Использование for…of с классами ES6

Можно использовать for..of для итерации по списку данных в экземпляре класса.

Класс Profiles обладает свойством , которое содержит массив пользователей. Возможно, потребуется отобразить эти данные в приложении с помощью for…of. Пробуем:

Очевидно, for…of не сработает

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

  • Объект должен иметь свойство .
  • Функция  должна возвращать итератор.
  •  должен реализовывать функцию .

Свойство @@iterator определяется с помощью константы .

Запускаем:

Свойство profiles отображено.

Управление временным континуумом с Node.js

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

«Когда я скажу» Выполнение ~

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

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

Функция выполнится через время, максимально приближенное к
1500 миллисекундам (или 1.5 секунды), из-за вызова .

Нельзя полагаться на то, что тайм-аут выполнится после этого точного количества миллисекунд.
Это связано с тем, что другой исполняемый код, который блокирует или удерживает цикл событий,
отодвигает выполнение тайм-аута на задний план. Единственной гарантией является то, что
тайм-аут не будет выполнен раньше, чем заданный интервал.

возвращает объект , который можно использовать в качестве ссылки
на тайм-аут, который был установлен. Этот объект можно использовать для отмены тайм-аута (см. ниже), а также для изменения поведения при выполнении (см. ниже).

«Сразу после этого» Выполнение ~

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

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

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

возвращает объект , который можно использовать для отмены
запланированного immediate (см. ниже).

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

«Бесконечный цикл» Выполнение ~

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

В примере выше будет выполняться каждые 1500 миллисекунд
или 1.5 секунд, до тех пор, пока ее не остановят (см. ниже).

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

Symbol.iterator

Мы легко поймём принцип устройства перебираемых объектов, создав один из них.

Например, у нас есть объект. Это не массив, но он выглядит подходящим для .

Например, объект , который представляет собой диапазон чисел:

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

  1. Когда цикл запускается, он вызывает этот метод один раз (или выдаёт ошибку, если метод не найден). Этот метод должен вернуть итератор – объект с методом .
  2. Дальше работает только с этим возвращённым объектом.
  3. Когда хочет получить следующее значение, он вызывает метод этого объекта.
  4. Результат вызова должен иметь вид , где означает, что итерация закончена, в противном случае содержит очередное значение.

Вот полная реализация с пояснениями:

Обратите внимание на ключевую особенность итераторов: разделение ответственности

  • У самого нет метода .
  • Вместо этого другой объект, так называемый «итератор», создаётся вызовом , и именно его генерирует значения.

Таким образом, итератор отделён от самого итерируемого объекта.

Технически мы можем объединить их и использовать сам как итератор, чтобы упростить код.

Например, вот так:

Теперь возвращает сам объект : у него есть необходимый метод , и он запоминает текущее состояние итерации в . Короче? Да. И иногда такой способ тоже хорош.

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

Бесконечные итераторы

Можно сделать бесконечный итератор. Например, будет бесконечным при . Или мы можем создать итерируемый объект, который генерирует бесконечную последовательность псевдослучайных чисел. Это бывает полезно.

Метод не имеет ограничений, он может возвращать всё новые и новые значения, это нормально.

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

Definition and Usage

The do/while statement creates a loop that executes a block of code once,
before checking if the condition is true, then it will repeat the loop as long
as the condition is true.

The do/while statement is used when you want to run a loop at least
one time, no matter what.

JavaScript supports different kinds of loops:

  • for — loops through a block of code a number of times
  • for/in — loops through the properties of an object
  • for/of — loops through the values of an iterable object
  • while — loops through a block of code while a specified condition is true
  • do/while — loops through a block of code once, and then repeats the loop while a specified condition is true

Структура оператора forStructure of the for statement

Оператор определяет разделы инициализатора, условия и итератора:The statement defines initializer, condition, and iterator sections:

Все три раздела добавляются по желанию.All three sections are optional. Тело цикла является оператором или блоком операторов.The body of the loop is either a statement or a block of statements.

В следующем примере показан оператор со всеми определенными разделами:The following example shows the statement with all of the sections defined:

Раздел инициализатораThe initializer section

Операторы в разделе инициализатора выполняются только один раз перед входом в цикл.The statements in the initializer section are executed only once, before entering the loop. Раздел инициализатора представляет собой один из следующих объектов:The initializer section is either of the following:

  • Объявление и инициализация локальной переменной цикла, к которой невозможно получить доступ вне цикла.The declaration and initialization of a local loop variable, which can’t be accessed from outside the loop.

  • Ноль или более выражений операторов из следующего списка, разделенные запятыми:Zero or more statement expressions from the following list, separated by commas:

    • оператор присваиванияassignment statement

    • вызов методаinvocation of a method

    • префиксное или постфиксное выражение , такое как или prefix or postfix expression, such as or

    • префиксное или постфиксное выражение , такое как или prefix or postfix expression, such as or

    • создание объекта с помощью оператора newcreation of an object by using the new operator

    • выражение awaitawait expression

Раздел инициализатора в приведенном выше примере объявляет и инициализирует локальную переменную цикла :The initializer section in the example above declares and initializes the local loop variable :

Раздел условияThe condition section

Раздел условия, если он определен, должен быть логическим выражением.The condition section, if present, must be a boolean expression. Это выражение оценивается перед каждой итерацией цикла.That expression is evaluated before every loop iteration. Если раздел условия отсутствует или логическое выражение имеет значение , выполняется следующая итерация цикла. В противном случае выполняется выход из цикла.If the condition section is not present or the boolean expression evaluates to , the next loop iteration is executed; otherwise, the loop is exited.

Раздел условия в приведенном выше примере определяет, завершится ли цикл в зависимости от значения локальной переменной цикла:The condition section in the example above determines if the loop terminates based on the value of the local loop variable:

Раздел итератораThe iterator section

Раздел итератора определяет, что происходит после каждой итерации тела цикла.The iterator section defines what happens after each iteration of the body of the loop. Раздел итератора содержит ноль или более следующих выражений оператора, разделенных запятыми:The iterator section contains zero or more of the following statement expressions, separated by commas:

  • оператор присваиванияassignment statement

  • вызов методаinvocation of a method

  • префиксное или постфиксное выражение , такое как или prefix or postfix expression, such as or

  • префиксное или постфиксное выражение , такое как или prefix or postfix expression, such as or

  • создание объекта с помощью оператора newcreation of an object by using the new operator

  • выражение awaitawait expression

Раздел итератора в приведенном выше примере увеличивает локальную переменную цикла:The iterator section in the example above increments the local loop variable:

Число

Числовой тип данных () представляет как целочисленные значения, так и числа с плавающей точкой.

Существует множество операций для чисел, например, умножение , деление , сложение , вычитание и так далее.

Кроме обычных чисел, существуют так называемые «специальные числовые значения», которые относятся к этому типу данных: , и .

  • представляет собой математическую ∞. Это особое значение, которое больше любого числа.

    Мы можем получить его в результате деления на ноль:

    Или задать его явно:

  • означает вычислительную ошибку. Это результат неправильной или неопределённой математической операции, например:

    Значение «прилипчиво». Любая операция с возвращает :

    Если где-то в математическом выражении есть , то результатом вычислений с его участием будет .

Математические операции – безопасны

Математические операции в JavaScript «безопасны». Мы можем делать что угодно: делить на ноль, обращаться с нечисловыми строками как с числами и т.д.

Скрипт никогда не остановится с фатальной ошибкой (не «умрёт»). В худшем случае мы получим как результат выполнения.

Специальные числовые значения относятся к типу «число». Конечно, это не числа в привычном значении этого слова.

Подробнее о работе с числами мы поговорим в главе Числа.

Переход к следующей итерации: continue

Директива – «облегчённая версия» . При её выполнении цикл не прерывается, а переходит к следующей итерации (если условие все ещё равно ).

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

Например, цикл ниже использует , чтобы выводить только нечётные значения:

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

Директива позволяет избегать вложенности

Цикл, который обрабатывает только нечётные значения, мог бы выглядеть так:

С технической точки зрения он полностью идентичен. Действительно, вместо можно просто завернуть действия в блок .

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

Нельзя использовать справа от оператора „?“

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

Например, если мы возьмём этот код:

…и перепишем его, используя вопросительный знак:

…то будет синтаксическая ошибка.

Это ещё один повод не использовать оператор вопросительного знака вместо .

Рекурсивный setTimeout

Есть два способа запускать что-то регулярно.

Один из них . Другим является рекурсивный . Например:

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

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

Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд…
Вот псевдокод:

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

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

Сравним два фрагмента кода. Первый использует :

Второй использует рекурсивный :

Для внутренний планировщик будет выполнять каждые 100 мс:

Обратили внимание?

Реальная задержка между вызовами с помощью меньше, чем указано в коде!

Это нормально, потому что время, затраченное на выполнение , использует часть заданного интервала времени.

Вполне возможно, что выполнение будет дольше, чем мы ожидали, и займёт более 100 мс.

В данном случае движок ждёт окончания выполнения и затем проверяет планировщик и, если время истекло, немедленно запускает его снова.

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

Ниже представлено изображение, показывающее процесс работы рекурсивного :

Рекурсивный гарантирует фиксированную задержку (здесь 100 мс).

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

Сборка мусора и колбэк setTimeout/setInterval

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

Для функция остаётся в памяти до тех пор, пока не будет вызван .

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

Цикл for/in

Цикл for/in использует ключевое слово for, но он в корне отличается от обычного цикла for. Цикл for/in имеет следующий синтаксис:

for (переменная in объект)
{
    инструкция
}

В качестве переменной здесь обычно используется имя переменной, но точно так же можно использовать инструкцию var, объявляющую единственную переменную. Параметр объект — это выражение, возвращающее объект. И как обычно, инструкция — это инструкция или блок инструкций, образующих тело цикла.

Для обхода элементов массива естественно использовать обычный цикл for:

Инструкция for/in так же естественно позволяет выполнить обход свойств объекта:

Чтобы выполнить инструкцию for/in, интерпретатор JavaScript сначала вычисляет выражение объект. Если оно возвращает значение null или undefined, интерпретатор пропускает цикл и переходит к следующей инструкции. Если выражение возвращает простое значение, оно преобразуется в эквивалентный объект-обертку. В противном случае выражение возвращает объект. Затем интерпретатор выполняет по одной итерации цикла для каждого перечислимого свойства объекта. Перед каждой итерацией интерпретатор вычисляет значение выражения, сохраняет его в переменной и присваивает ему имя свойства (строковое значение).

Обратите внимание, что переменная в цикле for/in может быть любым выражением, возвращающим значение, которое можно использовать слева от оператора присваивания. Это выражение вычисляется в каждой итерации цикла, т.е

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

В действительности цикл for/in может совершать обход не по всем свойствам объекта, а только по перечислимым свойствам. Многочисленные встроенные методы, определяемые в базовом языке JavaScript, не являются перечислимыми. Например, все объекты имеют метод toString(), но цикл for/in не перечислит свойство toString. Кроме встроенных методов также не являются перечислимыми многие другие свойства встроенных объектов. При этом все свойства и методы, определяемые пользователем, являются перечислимыми. Унаследованные свойства, которые были определены пользователем, также перечисляются циклом for/in.

Если в теле цикла for/in удалить свойство, которое еще не было перечислено, это свойство перечислено не будет. Если в теле цикла создать новые свойства, то обычно такие свойстве не будут перечислены. (Однако некоторые реализации могут перечислять унаследованные свойства, добавленные в ходе выполнения цикла.)

Оцените статью
Рейтинг автора
5
Материал подготовил
Илья Коршунов
Наш эксперт
Написано статей
134
Добавить комментарий