Примечание
ЭТО АРХИВНАЯ ВЕРСИЯ КУРСА!
Материалы предназначаются для пересдающих дисциплину "ОП.04 - Основы алгоритмизации и программирования" в соответствии с учебными планами СПО годов набора ДО 2023-го.
Материалы были перенесены со старого сайта с минимальной доработкой, поэтому не все возможности курса могут работать как ожидается, где-то может слететь форматирование.
Домашние задания в рамках курса проверяться не будут!
ОП.04 - 11 - Массивы и объекты. Циклы и их виды
Код примера для практической работы
Массивы
Для хранения строго упорядоченных данных существует особая структура данных, которая называется массив, Array.
Массив — структура данных, хранящая набор значений (элементов массива), идентифицируемых по индексу или набору индексов, принимающих значения из некоторого заданного непрерывного диапазона.
Наряду с объектами, которые мы рассмотрим далее, массивы относятся к составным типам данных, и называются так потому что по сути своей состоят из данных более простых типов (чисел, строк и других примитивов). При этом, в JavaScript элементами одного массива могут быть другие массивы, и такие конструкции носят название многомерных массивов (по количеству уровней вложенности).
Существует два варианта синтаксиса для создания пустого массива: let arr = new Array(); или let arr = [];.
Практически всегда используется второй вариант синтаксиса.
Такое объявление называют литералом массива.
В скобках мы можем указать начальные значения элементов:
let fruits = ["Яблоко", "Апельсин", "Слива"];
console.log( fruits );
Элементы массива нумеруются, начиная с нуля, при этом номера элементов в массивах называются индексами.
Мы можем получить элемент, указав его индекс в квадратных скобках:
let fruits = ["Яблоко", "Апельсин", "Слива"];
console.log( fruits[0] ); // Яблоко
console.log( fruits[1] ); // Апельсин
console.log( fruits[2] ); // Слива
Также, используя индексы мы можем заменить элемент:
let fruits = ["Яблоко", "Апельсин", "Слива"];
fruits[2] = 'Груша';
console.log( fruits ); // теперь ["Яблоко", "Апельсин", "Груша"]
...или же добавить новый к существующему массиву:
let fruits = ["Яблоко", "Апельсин", "Слива"];
fruits[3] = 'Лимон';
console.log( fruits ); // теперь ["Яблоко", "Апельсин", "Груша", "Лимон"]
Общее число элементов массива содержится в его свойстве length:
let fruits = ["Яблоко", "Апельсин", "Слива"];
console.log( fruits.length ); // 3
В массиве могут храниться элементы любого типа, например:
let arr = [ 'Яблоко', true, 2, NaN, undefined, ["Яблоко", "Апельсин", "Слива"] ];
console.log( arr );
[!INFO]
Не все языки позволяют себе такие вольности. Например, на языке C (Си) мы сначала должны четко задать длины массива и типы хранимых в нем значений, чтобы выделить строго определенное количество памяти под них. В этом смысле, язык JavaScript позволяет работать с массивами существенно более просто и комфортно (правда, ценой больших расходов памяти).
Допустим, нам нужен последний элемент массива. Некоторые языки программирования позволяют использовать отрицательные индексы для той же цели, как-то так: fruits[-1]. Однако, в JavaScript такая запись не сработает. Её результатом будет undefined, поскольку индекс в квадратных скобках понимается буквально.
Однако, если мы обратим внимание на то что индексы начинаются с 0, а свойство длины массива length всегда возвращает нам целое число его элементов, то мы сможем явно вычислить индекс последнего элемента, а затем получить к нему доступ вот так: fruits[fruits.length - 1].
let fruits = ["Яблоко", "Апельсин", "Слива"];
console.log( fruits[fruits.length - 1] ); // Слива
[!INFO]
Существует и более простой способ, опирающийся на сравнительно новый синтаксисat, но эта возможность была добавлена в язык недавно, и в старых браузерах может не работать, поэтому пока рассматривать ее не будем.
Так как массивы в JavaScript — это особая структура данных, то она обладает некоторыми уникальными возможностями, присущими только ей. Такие особые возможности в программировании принято называть методами.
Чем больше элементов содержит массив, тем больше времени потребуется для того, чтобы их переместить, больше операций с памятью.
Методы, работающие с концом массива
pop
Удаляет последний элемент из массива и возвращает его:
let fruits = ["Яблоко", "Апельсин", "Слива"];
console.log( fruits.pop() ); // удаляем "Слива" и выводим его
console.log( fruits ); // Яблоко, Апельсин
И fruits.pop() и fruits[fruits.length - 1] возвращают последний элемент массива, но fruits.pop() также изменяет массив, удаляя соответствующий элемент.
push
Добавляет элемент в конец массива:
let fruits = ["Яблоко", "Апельсин"];
fruits.push("Груша");
console.log( fruits ); // Яблоко, Апельсин, Груша
Вызов fruits.push(...) равнозначен fruits[fruits.length] = ....
Методы, работающие с началом массива
shift
Удаляет из массива первый элемент и возвращает его:
let fruits = ["Яблоко", "Апельсин", "Груша"];
console.log( fruits.shift() ); // удаляем Яблоко и выводим его
console.log( fruits ); // Апельсин, Груша
unshift
Добавляет элемент в начало массива:
let fruits = ["Апельсин", "Груша"];
fruits.unshift('Яблоко');
console.log( fruits ); // Яблоко, Апельсин, Груша
Многомерные массивы
Как было сказано ранее, массивы могут содержать элементы, которые тоже являются массивами. Это можно использовать для создания многомерных массивов, например, для хранения матриц:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log( matrix[1][1] ); // 5, центральный элемент
Объекты
В JavaScript объекты используются очень часто, это одна из основ языка. Поэтому мы должны понять их, прежде чем углубляться куда-либо ещё. Объект может быть создан с помощью фигурных скобок {...} с необязательным списком свойств. Свойство — это пара «ключ: значение», где ключ — это строка (также называемая «именем свойства»), а значением может быть чем угодно.
[!INFO]
Мы можем представить объект в виде ящика с подписанными папками. Каждый элемент данных хранится в своей папке, на которой написан ключ. По ключу папку легко найти, удалить или добавить в неё что-либо.
Пустой объект можно создать, используя один из двух вариантов синтаксиса: let user = new Object(); или let user = {};.
Практически всегда используется второй вариант синтаксиса.
Такое объявление называют литералом объекта.
При использовании литерального синтаксиса {...} мы сразу можем поместить в объект несколько свойств в виде пар «ключ: значение»:
let user = { // объект
name: "Павел", // под ключом "name" хранится значение "Павел"
age: 34 // под ключом "age" хранится значение 34
};
console.log( user ); // выведется весь объект
У каждого свойства есть ключ (также называемый «имя» или «идентификатор»). После имени свойства следует двоеточие :, и затем указывается значение свойства. Если в объекте несколько свойств, то они перечисляются через запятую.
В объекте user сейчас находятся два свойства:
- Первое свойство с именем
nameи значением"Павел". - Второе свойство с именем
ageи значением34.
Можно сказать, что наш объект user — это ящик с двумя папками, подписанными «name» и «age». Мы можем в любой момент добавить в него новые папки, удалить папки или прочитать содержимое любой папки.
Для обращения к свойствам используется запись «через точку»:
let user = {
name: "Павел",
age: 34
};
// получаем свойства объекта:
console.log( user.name ); // Павел
console.log( user.age ); // 34
Значение объекта может быть любого типа.
Давайте добавим свойство с логическим значением:
let user = {
name: "Павел",
age: 34
};
user.isAdmin = true;
console.log( user ); // выведется объект с новым свойством
Для удаления свойства мы можем использовать оператор delete:
let user = {
name: "Павел",
age: 34
};
delete user.age;
console.log( user ); // выведется объект уже без свойства age
[!TIP]
Для имен свойств объекта не действуют ограничения, применимые к именам переменных, но хорошей практикой является называть их используя символы латинского алфавита, цифры и символы нижнего подчеркивания, и при этом избегать имен, начинающихся с цифр.
Циклы и их виды
При написании программ зачастую встаёт задача сделать однотипное действие много раз. Например, вывести товары из списка один за другим. Или просто перебрать все числа от 1 до 10 и для каждого выполнить одинаковое действие.
Для многократного повторения одного участка кода в программировании используют циклы.
Цикл — разновидность управляющей конструкции в языках программирования, предназначенная для организации многократного исполнения набора инструкций при определенных условиях.
Как правило, принято разделять циклы с предусловием и с постусловием, но иногда отдельно рассматривают и циклы с заданным количеством итераций (повторений).
В языке JavaScript для каждого из перечисленных видов циклов существует своя определенная синтаксическая конструкция.
Цикл с предусловием — while
Цикл с предусловием — цикл, который выполняется, пока истинно некоторое условие, указанное перед его началом. Это условие проверяется до выполнения тела цикла, поэтому тело может быть не выполнено ни разу (если условие с самого начала ложно).
Цикл while имеет следующий синтаксис:
while (условие) {
// код, также называемый "телом цикла"
}
Код из тела цикла выполняется, пока условие условие истинно.
[!INFO]
Пример для пониманияПеред нами тарелка с супом, и пока она не пуста, в рамках каждой итерации нам необходимо зачерпнуть ложной какое-то количество супа. Если изначально тарелка пуста, то нет смысла даже единожды пытаться зачерпнуть суп.
Например, цикл ниже выводит i, пока i < 3:
let i = 0;
while (i < 3) { // выводит 0, затем 1, затем 2
console.log( i );
i++;
}
[!INFO]
Одно выполнение тела цикла по-научному называется итерация. А переменную, которая изменяется в рамках каждой итерации, как бы отсчитывая каждый шаг цикла, часто называют итератором.
Цикл в примере выше совершает три итерации, на каждой из которых инкрементируется (i++) значение итератора i. Значение итератора i также может декрементироваться (i--), или даже меняться произвольно (i = i * 2).
Если бы строка i++ отсутствовала в примере выше, то цикл бы повторялся (в теории) бесконечно. На практике, конечно, браузер не позволит такому случиться, он предоставит пользователю возможность остановить «зависший» скрипт, а JavaScript на стороне сервера придётся «убить» процесс.
Любое выражение или переменная может быть условием цикла, а не только сравнение: условие while вычисляется и преобразуется в логическое значение подобно тому как это было ранее для условий if (...).
Цикл с постусловием — do..while
Цикл с постусловием — цикл, в котором условие проверяется после выполнения тела цикла. Отсюда следует, что тело всегда выполняется хотя бы один раз.
Цикл do..while имеет следующий синтаксис:
do {
// тело цикла
} while (условие);
Цикл сначала выполнит тело, а затем проверит условие условие, и пока его значение равно true, он будет выполняться снова и снова.
[!INFO]
Пример для пониманияПеред нами закрытая дверь и дверной звонок, мы знаем что хозяин дома и нам очень нужно чтобы дверь открыли, и поэтому мы должны позвонить хотя бы один раз и намерены продолжать звонить до тех пор пока дверь не откроется.
Например:
let i = 0;
do {
console.log( i );
i++;
} while (i < 3);
Такая форма синтаксиса оправдана, если вы хотите, чтобы тело цикла выполнилось хотя бы один раз, даже если условие окажется ложным.
Цикл с заданным количеством итераций — for
Более сложный, но при этом самый распространённый цикл — цикл for.
Выглядит он так:
for (начало; условие; шаг) {
// тело цикла
}
То есть, начало выполняется один раз, а затем каждая итерация заключается в проверке условия, после которой выполняется тело и шаг.
[!INFO]
Пример для пониманияНа тренировке нам во что бы то ни стало нужно выполнить упражнение, состоящее из четко заданного числа повторений, например 20 отжиманий. При этом не имеет значения, будем ли мы считать от 0 до 20 (инкрементировать значение итератора) или же от 20 до 0 (декрементировать значение итератора), количество шагов при этом не изменится.
Цикл ниже выполняет console.log(i) для i от 0 до (но не включая) 3:
for (let i = 0; i < 3; i++) {
console.log(i); // выведет 0, затем 1, затем 2
}
Здесь переменная счётчика i (итератор) была объявлена прямо в цикле. Это так называемое «встроенное» объявление переменной. Такие переменные существуют только внутри цикла.
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ошибка, нет такой переменной
Вместо объявления новой переменной мы можем использовать уже существующую:
let i = 0;
for (i = 0; i < 3; i++) { // используем существующую переменную
console.log(i); // 0, 1, 2
}
console.log(i); // 3, переменная доступна, т.к. была объявлена снаружи цикла
[!INFO]
Такое поведение принято называть областью видимости переменной. Мы поговорим об этом подробнее когда будем изучать функции, а пока просто будем иметь ввиду такую особенность.
Прерывание цикла — break
Обычно цикл завершается автоматически как только вычисляемое значение его условия становится false.
Но мы можем выйти из цикла в любой момент с помощью специальной директивы break.
Например, следующий код подсчитывает сумму вводимых чисел до тех пор, пока посетитель их вводит, а затем — выдаёт:
let sum = 0;
while (true) {
let value = +prompt("Введите число", '');
if (!value) break;
sum += value;
}
alert( 'Сумма: ' + sum );
Директива break полностью прекращает выполнение цикла и передаёт управление на строку за его телом, то есть на alert.
Вообще, сочетание «бесконечного цикла с директивой break» — отличная штука для тех ситуаций, когда условие, по которому нужно прерваться, находится не в начале или конце цикла, а посередине или даже в нескольких местах его тела, либо может произойти динамически в любой момент в процессе его выполнения.
[!INFO]
Обычно именно таким образом в компьютерных играх проверяется жив ли персонаж игрока, постоянно проверяя значение его очков здоровья в рамках т.н. игрового цикла, то есть каждого минимального такта расчета состояния игровой сцены.
Переход к следующей итерации — continue
Директива continue — «облегчённая версия» break. При её выполнении цикл не прерывается, а переходит к следующей итерации (если условие все ещё равно true).
Её используют, если понятно, что на текущем повторе цикла делать больше нечего.
Например, цикл ниже использует continue, чтобы выводить только нечётные значения:
for (let i = 0; i < 10; i++) {
// если true, пропустить оставшуюся часть тела цикла
if (i % 2 == 0) continue;
console.log(i); // 1, затем 3, 5, 7, 9
}
Для чётных значений i, директива continue прекращает выполнение тела цикла и передаёт управление на следующую итерацию for (со следующим числом). Таким образом console.log вызывается только для нечётных значений.
Вложенные циклы
Практически, довольно часто возникает необходимость использования т.н. вложенных циклов, то есть синтаксических конструкций в рамках которых на каждой итерации одного цикла выполняется некоторое количество итераций другого.
В рамках некоторого простого "бытового" примера можно представить ситуацию когда необходимо снять колеса на автомобиле, при этом чтобы заменить все 4 колеса, необходимо для каждого открутить 5 крепежных болтов.
На языке JavaScript подобная задача могла бы выглядеть так:
for (let i = 1; i <= 4; i++) {
console.log(`Откручиваем ${i}-е колесо`);
for (let j = 1; j <= 5; j++) {
console.log(`Откручиваем ${j}-й крепежный болт ${i}-го колеса`);
}
}
console.log(`Фух, вроде все открутили...`);
Перебор элементов массивов
Одним из самых старых способов перебора элементов массива является цикл for по цифровым индексам:
let arr = ["Яблоко", "Апельсин", "Груша"];
for (let i = 0; i < arr.length; i++) {
console.log( arr[i] );
}
Но для массивов возможен и другой вариант цикла, for..of:
let fruits = ["Яблоко", "Апельсин", "Слива"];
// проходит по значениям
for (let fruit of fruits) {
console.log( fruit );
}
Цикл for..of не предоставляет доступа к номеру текущего элемента, только к его значению, но в большинстве случаев этого достаточно. А также это короче.
Что почитать по теме
- Современный учебник JavaScript - Массивы
- Современный учебник JavaScript - Методы массивов
- Современный учебник JavaScript - Объекты
- Современный учебник JavaScript - Циклы while и for
- W3Schools - JavaScript Arrays
- W3Schools - JavaScript Array Methods
- W3Schools - JavaScript Objects
- W3Schools - JavaScript For Loop
- W3Schools - JavaScript While Loop
- W3Schools - JavaScript Iterables