Loops and iteration

How To Break forEach loop in Javascript

You can’t break from the forEach function. Instead, you should use a normal for loop.

In some cases, Array.every() function will probably fulfill the requirements.

#How do I go to the next iteration of a Javascript Array.forEach() loop

You can return if you want to skip the current iteration.

Since you’re in a function, if you return before doing anything else, then you have effectively skipped the execution of the code below the return statement.

JavaScript’s forEach works a bit different from the one that might be used from other languages for each loop.

If you read on the MDN, it says that the function is executed for each of the elements in the array, in the ascending order. If we want to continue to the next item, that is, run the next function, you can return a current function without having it do any computation.

Работа с массивами JS — методы pop и push

Метод pop в JavaScript удаляет элемент массива и возвращает его.

В следующем примере показано, как «Осел» извлекается из массива:

var fruits = 
alert("Я удаляю "+fruits.pop())
// Теперь у нас только 
alert("Теперь размер массива: "+fruits.length) // осел удален

Обратите внимание, что pop изменяет сам массив. Аналог pop — это метод push, который добавляет элемент в массив

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

Аналог pop — это метод push, который добавляет элемент в массив. Например, мы забыли добавить персик:

var fruits = 
fruits.push("Peach");
// теперь у нас есть 
alert("Последний элемент:"+fruits)

Задание для самостоятельного выполнения

  1. Создайте массив styles с элементами “Jazz”, “Blues”;
  2. Добавьте значение «Rock’n’Roll«;
  3. Замените второе значение с конца значением «Classic«. У вас должен получиться массив: “Jazz”, ”Classic”, ”Rock’n’Roll”. Код должен работать для любой длины массива;
  4. Извлеките последнее значение из массива и выведите его через alert.

Решение

// 1
var styles = 

// 2
styles.push("Rock'n'Roll") // или: styles = "Rock'n'Roll"

// 3 
styles = "Classic"

// 4
alert( styles.pop() )

Добавление/удаление элементов

Мы уже знаем методы, которые добавляют и удаляют элементы из начала или конца:

  • – добавляет элементы в конец,
  • – извлекает элемент из конца,
  • – извлекает элемент из начала,
  • – добавляет элементы в начало.

Есть и другие.

Как удалить элемент из массива?

Так как массивы – это объекты, то можно попробовать :

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

Это нормально, потому что всё, что делает – это удаляет значение с данным ключом . Это нормально для объектов, но для массивов мы обычно хотим, чтобы оставшиеся элементы сдвинулись и заняли освободившееся место. Мы ждём, что массив станет короче.

Поэтому для этого нужно использовать специальные методы.

Метод arr.splice(str) – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы.

Его синтаксис:

Он начинает с позиции , удаляет элементов и вставляет на их место. Возвращает массив из удалённых элементов.

Этот метод проще всего понять, рассмотрев примеры.

Начнём с удаления:

Легко, правда? Начиная с позиции , он убрал элемент.

В следующем примере мы удалим 3 элемента и заменим их двумя другими.

Здесь видно, что возвращает массив из удалённых элементов:

Метод также может вставлять элементы без удаления, для этого достаточно установить в :

Отрицательные индексы разрешены

В этом и в других методах массива допускается использование отрицательного индекса. Он позволяет начать отсчёт элементов с конца, как тут:

Метод arr.slice намного проще, чем похожий на него .

Его синтаксис:

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

Это похоже на строковый метод , но вместо подстрок возвращает подмассивы.

Например:

Можно вызвать и вообще без аргументов: создаёт копию массива . Это часто используют, чтобы создать копию массива для дальнейших преобразований, которые не должны менять исходный массив.

Метод arr.concat создаёт новый массив, в который копирует данные из других массивов и дополнительные значения.

Его синтаксис:

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

В результате мы получаем новый массив, включающий в себя элементы из , а также , и так далее…

Если аргумент – массив, то все его элементы копируются. Иначе скопируется сам аргумент.

Например:

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

…Но если объект имеет специальное свойство , то он обрабатывается как массив: вместо него добавляются его числовые свойства.

Для корректной обработки в объекте должны быть числовые свойства и :

Инкремент/декремент

Одной из наиболее частых числовых операций является увеличение или уменьшение на единицу.

Для этого существуют даже специальные операторы:

  • Инкремент увеличивает переменную на 1:

  • Декремент уменьшает переменную на 1:

Важно:

Инкремент/декремент можно применить только к переменной. Попытка использовать его на значении, типа 5++, приведёт к ошибке.

Операторы и могут быть расположены не только после, но и до переменной.

  • Когда оператор идёт после переменной — это «постфиксная форма»: .
  • «Префиксная форма» — это когда оператор идёт перед переменной: .

Обе эти инструкции делают одно и то же: увеличивают на .

Есть ли разница между ними? Да, но увидеть её мы сможем, только если будем использовать значение, которое возвращают .

Давайте проясним этот момент. Как мы знаем, все операторы возвращают значение. Операторы инкремента/декремента не исключение. Префиксная форма возвращает новое значение, в то время как постфиксная форма возвращает старое (до увеличения/уменьшения числа).

Чтобы увидеть разницу, вот небольшой пример:

В строке префиксная форма увеличивает и возвращает новое значение . Так что покажет .

Теперь посмотрим на постфиксную форму:

В строке постфиксная форма также увеличивает , но возвращает старое значение (которое было до увеличения). Так что покажет .

Подведём итоги:

  • Если результат оператора не используется, а нужно только увеличить/уменьшить переменную, тогда без разницы, какую форму использовать:

  • Если хочется тут же использовать результат, то нужна префиксная форма:

  • Если нужно увеличить и при этом получить значение переменной до увеличения – нужна постфиксная форма:

Инкремент/декремент можно использовать в любых выражениях

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

Например:

Сравните с:

Хотя технически здесь всё в порядке, такая запись обычно делает код менее читабельным. Одна строка выполняет множество действий – нехорошо.

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

Лучше использовать стиль «одна строка – одно действие»:

Пример класса jQuery.each()

В этом примере показано, как перебрать каждый элемент с классом productDescription:

<div class="productDescription">Красный</div>
<div>Розовый</div>
<div class="productDescription">Оранжевый</div>
<div class="generalDescription">Синевато-зеленый</div>
<div class="productDescription">Зеленый</div>

Вместе с селектором мы используем вспомогательную функцию each() вместо метода each():

$.each($('.productDescription'), function (index, value) { 
  console.log(index + ':' + $(value).text()); 
});

Результат использования jQuery each function будет следующим: 0:Красный, 1: Оранжевый, 2:Зеленый.

Нам не нужно использовать индекс и значение. Это параметры, которые помогают определить, какой элемент DOM в настоящее время обрабатывается циклом. Кроме этого в сценарии можно использовать более удобный метод each():

$('.productDescription').each(function () { 
  console.log($(this).text());
});

Результат:

Красный Оранжевый Зеленый

Нужно обернуть элемент DOM в новый экземпляр jQuery. Мы используем метод text() для получения текста элемента.

link Pseudo-Selectors

1

2

3

4

5

6

7

8

9

10

11

12

Note: When using the and pseudo-selectors, jQuery tests the actual visibility of the element, not its CSS or properties. jQuery looks to see if the element’s physical height and width on the page are both greater than zero.

However, this test doesn’t work with elements. In the case of jQuery does check the CSS property, and considers an element hidden if its property is set to .

Elements that have not been added to the DOM will always be considered hidden, even if the CSS that would affect them would render them visible. See the Manipulating Elements section to learn how to create and add elements to the DOM.

Работа с массивами JS — удаление из массива

Как мы знаем, массивы — это объекты, поэтому мы могли бы использовать delete, чтобы удалить значение:

var arr = 
delete arr
// теперь arr = 
alert(arr) // не задано

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

Оператор delete удаляет пару ключ-значение, и это все. Естественно, так как массив — это только хэш, позиция удаленного элемента становится undefined.

Чаще всего нам нужно удалить элемент, не оставляя «дыр» между индексами. Существует еще один метод, который поможет нам в этом.

Метод splice

Метод splice может удалять элементы и заменять их в JavaScript многомерных массивах. Его синтаксис:

arr.splice(index, deleteCount)

Удаляет элемент deleteCount, начиная с index, а затем вставляет на его место elem1, …, elemN.

Давайте рассмотрим несколько примеров:

var arr = 
arr.splice(1, 1)  // удалить 1 элемент, начиная с индекса 1
alert( arr.join(',') ) //  (1 элемент удален)

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

var arr = 
arr.splice(0, 1)  // удаляем 1 элемент, начиная с индекса 0
alert( arr ) // "to" стал первым элементом

В следующем примере показано, как заменять элементы:

var arr = ;
// remove 3 first elements and add two
arr.splice(0, 3, "Come", "here") 
alert( arr ) // 

Метод splice возвращает массив удаленных элементов:

var arr = ;
// удаляем 2 первых элемента 
var removed = arr.splice(0, 2) 
alert( removed ) // "Go", "to" <-- массив удаленных элементов
splice может вставлять элементы, задайте 0 для deleteCount.
var arr = ;
// со второй позиции 
// удаляем 0 
// и вставляем "my", "sweet"
arr.splice(2, 0, "my", "sweet") 
alert( arr) // "Go", "to", "my", "sweet", "home"

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

var arr = 
// для элемента -1 (предпоследнего)
// удаляем 0 элементов, 
// и вставляем 3 и 4
arr.splice(-1, 0, 3, 4)
alert(arr)  // 1,2,3,4,5

Задание для самостоятельного выполнения

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

var obj = {
	className: 'open menu'
}

Напишите функцию removeClass(obj, cls), которая удаляет класс cls, если он задан:

removeClass(obj, 'open') // obj.className='menu'

removeClass(obj, 'blabla')  // без изменений (класса для удаления не существует)

Решение

Нужно разделить className на части и перебрать эти части через цикл. Если найдено совпадение, оно удаляется из JavaScript массива объектов, а затем добавляется обратно в конец.

Немного оптимизируем это:

function removeClass(elem, cls) {
  for(var c = elem.className.split(' '), i=c.length-1; i>=0; i--) {
    if (c == cls) c.splice(i,1)
  }
	
  elem.className = c.join(' ')
}
var obj = { className: 'open menu' }
removeClass(obj, 'open')
removeClass(obj, 'blabla')
alert(obj.className)   // menu

В приведенном выше примере переменная c задана в начале цикла, и для i задан ее последний индекс.

Сам цикл выполняется в обратном направлении, заканчиваясь условием i>=0. Это сделано потому, что i>=0 проверяется быстрее, чем i. Что ускоряет поиск свойства в c.

MAP-массивы

Создание MAP-массива с данными

var mapArray = new Map(,
    ,
    ,
    
]);

Несколько операций, которые можно использовать в MAP-массиве

Добавление нового ключа и значения, либо изменение имеющегося значения ключа в массиве

mapArray.set('availability', true);

Удаление ключа и значения

mapArray.delete('quantity');

Полная очистка массива

mapArray.clear();

Проверка наличия ключа в массиве

mapArray.has('id');

если такой ключ есть, вернёт true

Список ключей в массиве

mapArray.keys();

Список значений в массиве

mapArray.values();

Показать значение ключа

mapArray.get('name');

Показать число элементов в массиве

mapArray.size;

Список ключей и элементов массива

mapArray.entries();

На этом пока всё.

Add/remove items

We already know methods that add and remove items from the beginning or the end:

  • – adds items to the end,
  • – extracts an item from the end,
  • – extracts an item from the beginning,
  • – adds items to the beginning.

Here are a few others.

How to delete an element from the array?

The arrays are objects, so we can try to use :

The element was removed, but the array still has 3 elements, we can see that .

That’s natural, because removes a value by the . It’s all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.

So, special methods should be used.

The arr.splice method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements.

The syntax is:

It modifies starting from the index : removes elements and then inserts at their place. Returns the array of removed elements.

This method is easy to grasp by examples.

Let’s start with the deletion:

Easy, right? Starting from the index it removed element.

In the next example we remove 3 elements and replace them with the other two:

Here we can see that returns the array of removed elements:

The method is also able to insert the elements without any removals. For that we need to set to :

Negative indexes allowed

Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here:

The method arr.slice is much simpler than similar-looking .

The syntax is:

It returns a new array copying to it all items from index to (not including ). Both and can be negative, in that case position from array end is assumed.

It’s similar to a string method , but instead of substrings it makes subarrays.

For instance:

We can also call it without arguments: creates a copy of . That’s often used to obtain a copy for further transformations that should not affect the original array.

The method arr.concat creates a new array that includes values from other arrays and additional items.

The syntax is:

It accepts any number of arguments – either arrays or values.

The result is a new array containing items from , then , etc.

If an argument is an array, then all its elements are copied. Otherwise, the argument itself is copied.

For instance:

Normally, it only copies elements from arrays. Other objects, even if they look like arrays, are added as a whole:

…But if an array-like object has a special property, then it’s treated as an array by : its elements are added instead:

Conclusion

In this post, we’ve demonstrated how to use the function to iterate over DOM elements, arrays and objects. It’s a powerful and time-saving little function that developers should have in their toolkits.

And if jQuery isn’t your thing, you might want to look at using JavaScript’s native Object.keys() and Array.prototype.forEach() methods. There are also libraries like foreach which let you iterate over the key value pairs of either an array-like object or a dictionary-like object.

Remember: and are two different methods defined in two different ways.

This popular article was updated in 2020 to reflect current best practices and to update the conclusion’s advice on native solutions using modern JavaScript. For more in-depth JavaScript knowledge, read our book, JavaScript: Novice to Ninja, 2nd Edition.

Что такое ассоциативный массив

Ассоциативный массив — это массив, у которого в качестве ключей используются строки.

Представить структуру данных ассоциативного массива можно как совокупность пар «ключ-значение». В ассоциативном массиве каждое значение связано с определённым ключом. Доступ к значению осуществляется по имени ключа.

Представить ассоциативный массив можно как ящичек, в котором находятся коробочки. Каждая коробочка в этом ящичке имеет имя (ключ) и содержимое (значение).

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

Присваивание

Давайте отметим, что в таблице приоритетов также есть оператор присваивания . У него один из самых низких приоритетов: .

Именно поэтому, когда переменной что-либо присваивают, например, , то сначала выполнится арифметика, а уже затем произойдёт присваивание с сохранением результата в .

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

Большинство операторов в JavaScript возвращают значение. Для некоторых это очевидно, например сложение или умножение . Но и оператор присваивания не является исключением.

Вызов записывает в и возвращает его.

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

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

Забавное применение присваивания, не так ли? Нам нужно понимать, как это работает, потому что иногда это можно увидеть в JavaScript-библиотеках.

Однако писать самим в таком стиле не рекомендуется. Такие трюки не сделают ваш код более понятным или читабельным.

Рассмотрим ещё одну интересную возможность: цепочку присваиваний.

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

Опять-таки, чтобы код читался легче, лучше разделять подобные конструкции на несколько строчек:

Польза от такого стиля особенно ощущается при быстром просмотре кода.

Что вам нужно для изучения JavaScript?

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

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

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

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

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

К счастью, как вы уже знаете, любой веб-браузер включает в себя интерпретатор JavaScript.

Чтобы писать и выполнять JavaScript-программы, достаточно установить любой современный интернет-браузер (например Yandex, Internet Explorer, Mozilla Firefox или Google Chrome).

Далее в этом учебнике вы увидите, что код на языке JavaScript можно встраивать непосредственно в HTML-файлы, в теги , и при загрузке HTML-файла этот код будет выполняться браузером. Стоит отметить, что вам не нужно поступать так всякий раз, когда требуется протестировать короткий фрагмент программного кода JavaScript.

Самый простой способ поэкспериментировать с кодом JavaScript – воспользоваться встроенным инструментом браузера веб-консоль (Web Console).

Как правило, веб-консоль можно запустить нажатием клавиши F12 или горячей комбинации клавиш – Ctrl+Shift+J. Обычно панель или окно типичного «инструмента разработчика» открывается в виде отдельной панели в верхней или нижней части окна браузера как изображено на рис. 1.

Панель включает множество вкладок, позволяющих исследовать структуру HTML-документа, стили CSS и т. д. Среди них имеется вкладка JavaScript Console, где можно вводить строки программного кода JavaScript и выполнять их.

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

С большинством примеров, приведенных в нашем учебнике, вы сможете поэкспериментировать прямо на странице сайта воспользовавшись встроенным инструментом — мини-редактором JS.

Это обычный HTML документ

Выходим обратно в HTML

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

Обратите внимание: В подобных простых экспериментах с JavaScript можно опускать теги , и в HTML-файле

Цикл for…of (новинка в ES6)

Цикл появился в стандарте ES6. Предназначен он для перебора итерируемых объектов, т.е. объектов, в которых реализован метод . Этот метод ещё называют итератором. Именно его и использует цикл для перебора объектов.

Метод имеется у , , , , , и других объектов.

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

// переменная, содержащаая строку
let str = 'Новый';
// посимвольный перебор строки
for (let char of str) {
  console.log(char);
}
// в консоль будет выведено: "Н", "о", "в", "ы", "й"

Пример использование цикла для перебора коллекции DOM-элементов:

let elements = document.querySelectorAll('p');
for (let element of elements) {
  console.log(element);
}

Пример использование цикла для перебора массива:

// массив
let superHeroes = ;
// перебор массива
for (let value of superHeroes) {
  console.log(value);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk"

Чем цикл for…of отличается от for…in?

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

Второе отличие заключается в том, что цикл перебирает объект так, как это определено в итераторе. Например, в итератор реализован так, что цикл пройдёт только по значениям в массиве и не будет включать в перебор другие (не индексные) свойства. Цикл организован по-другому, он перебирает все перечисляемые свойства (имена ключей) объекта, в том числе и наследуемые.

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

let superHeroes = ;
superHeroes.hero = 'Wasp';  

При использовании он переберёт все значения этого массива:

// цикл for...of
for (let value of superHeroes) {
  console.log(value);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk"

При использовании он переберёт все перечисляемые имена ключей этого объекта:

// цикл for...in
for (let key in superHeroes) {
  console.log(key);
}
// в консоль будет выведено: 0, 1, 2, "hero"

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

// цикл for...in
for (let key in superHeroes) {
  console.log(superHeroes);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk", "Wasp"

Самостоятельное создание итератора для объекта

Рассмотрим ещё один пример. В этом примере мы самостоятельно определим как должен итерироваться объект. Для этого создадим объект и определим ему итератор.

Создание итератора начинается с добавления к объекту специального метода. Этот метод необходимо спроектировать так, чтобы он возвращал значения последовательно (одно за другим). Название методу согласно стандарту необходимо определить с помощью символа . Итератор должен возвращать всего один метод . Этот метод в свою очередь тоже должен возвращать объект, состоящий из 2 свойств: и . Ключ — булевый. Он определяет есть ли ещё значения в последовательности ( — да, — нет). Ключ должен содержать следующее значение последовательности.

let car = {
  color: 'black',
  brand: 'Ford',
  // создадим итератор, используя символ
  () {
    // получим имена перечисляемых свойств объекта
    const keys = Object.keys(this);
    // создадим переменную (текущий индекс последовательности)
    let index = 0;
    return {
      next() {
        let done = index >= keys.length;
        let value = done ? undefined : keys;
        return {
          value,
          done
        }
      }
    }
  }
}

for (let key in car) {
  console.log(key + ' => ' + car);
}
// в консоль будет выведено: color => "black", brand => "Ford"

Итого

– коллекция пар ключ-значение.

Методы и свойства:

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

Отличия от обычного объекта :

  • Что угодно может быть ключом, в том числе и объекты.
  • Есть дополнительные методы, свойство .

– коллекция уникальных значений, так называемое «множество».

Методы и свойства:

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector