Google Apps Script: Как использовать цикл foreach?

Что такое цикл foreach и зачем он нужен?

Цикл foreach (или "для каждого") — это конструкция в программировании, предназначенная для итерации по элементам коллекции, например, массива или списка. В Google Apps Script, хотя и нет встроенного foreach, мы можем эмулировать его поведение, используя другие циклы и методы. Его основное преимущество — упрощение кода и повышение читаемости, особенно когда нужно выполнить одну и ту же операцию над каждым элементом.

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

Синтаксис цикла foreach в Google Apps Script

В Google Apps Script, традиционный цикл foreach не реализован как отдельный оператор. Однако, функциональность foreach достигается использованием метода forEach для массивов, или цикла for...in для перебора свойств объектов.

Для массивов:

/**
 * Пример использования forEach для перебора массива.
 * @param {string[]} array Массив строк для обработки.
 */
function processArray(array: string[]): void {
  array.forEach(function(element: string, index: number) {
    Logger.log('Элемент ' + index + ': ' + element);
  });
}

// Пример вызова:
const myArray: string[] = ['яблоко', 'банан', 'апельсин'];
processArray(myArray);

Для объектов (эмуляция foreach):

/**
 * Пример использования for...in для перебора свойств объекта.
 * @param {object} obj Объект для обработки.
 */
function processObject(obj: object): void {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) { // Важно: проверяем, что свойство не унаследовано
      Logger.log('Ключ: ' + key + ', Значение: ' + obj[key]);
    }
  }
}

// Пример вызова:
const myObject: object = { name: 'Иван', age: 30, city: 'Москва' };
processObject(myObject);

Отличия foreach от других циклов (for, while)

for: Требует явного управления счетчиком и условием завершения. Более гибкий, позволяет пропускать элементы или изменять шаг итерации.

while: Выполняет блок кода, пока условие истинно. Подходит, когда количество итераций заранее неизвестно.

forEach: Специализирован для перебора элементов массива. Компактный и читаемый, но менее гибкий, чем for. Не позволяет прервать цикл (break) или перейти к следующей итерации (continue) напрямую.

for...in: Используется для перебора свойств объекта. Важно проверять, является ли свойство собственным, чтобы избежать итерации по прототипным свойствам.

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

Простой пример перебора массива строк

/**
 * Перебирает массив строк и выводит каждый элемент в лог.
 * @param {string[]} names Массив имен.
 */
function printNames(names: string[]): void {
  names.forEach(function(name: string) {
    Logger.log(name);
  });
}

// Пример вызова:
const namesArray: string[] = ['Alice', 'Bob', 'Charlie'];
printNames(namesArray);

Доступ к индексу элемента при переборе массива

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

/**
 * Выводит индекс и значение каждого элемента массива.
 * @param {any[]} data Массив данных.
 */
function printIndexAndValue(data: any[]): void {
  data.forEach(function(value: any, index: number) {
    Logger.log('Индекс: ' + index + ', Значение: ' + value);
  });
}

// Пример вызова:
const dataArray: any[] = [10, 'hello', true];
printIndexAndValue(dataArray);

Изменение элементов массива внутри цикла foreach (осторожно!)

Хотя технически это возможно, изменение массива во время итерации с помощью forEach может привести к непредсказуемым результатам. Лучше избегать этого. Если необходимо изменить массив, рекомендуется создать новый массив с измененными значениями или использовать цикл for.

Пример (не рекомендуется):

/**
 * Пример изменения массива во время итерации (не рекомендуется).
 * @param {number[]} numbers Массив чисел.
 */
function modifyArray(numbers: number[]): void {
  numbers.forEach(function(number: number, index: number) {
    if (number < 0) {
      numbers[index] = 0; // Изменение элемента массива
    }
  });
  Logger.log(numbers); // Вывод: [1, 2, 0, 4, 0]
}

const numbersArray: number[] = [1, 2, -3, 4, -5];
modifyArray(numbersArray);

Лучше так:

/**
 * Пример создания нового массива с измененными значениями.
 * @param {number[]} numbers Массив чисел.
 * @returns {number[]} Новый массив с измененными значениями. 
 */
function createModifiedArray(numbers: number[]): number[] {
  const newNumbers: number[] = numbers.map(function(number: number) {
    return number < 0 ? 0 : number;
  });
  return newNumbers;
}

const originalNumbers: number[] = [1, 2, -3, 4, -5];
const modifiedNumbers: number[] = createModifiedArray(originalNumbers);
Logger.log(modifiedNumbers); // Вывод: [1, 2, 0, 4, 0]

Перебор многомерных массивов с использованием foreach

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

/**
 * Перебирает двухмерный массив и выводит каждый элемент.
 * @param {any[][]} matrix Двухмерный массив.
 */
function processMatrix(matrix: any[][]): void {
  matrix.forEach(function(row: any[]) {
    row.forEach(function(element: any) {
      Logger.log(element);
    });
  });
}

// Пример вызова:
const matrix: any[][] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
processMatrix(matrix);
Реклама

Цикл foreach для перебора объектов

Перебор свойств объекта с использованием `for…in` (альтернатива foreach для объектов)

В JavaScript и, следовательно, в Google Apps Script нет встроенного метода forEach непосредственно для объектов. Однако, цикл for...in позволяет перебирать свойства объекта.

/**
 * Перебирает свойства объекта и выводит ключи и значения.
 * @param {object} obj Объект для обработки.
 */
function iterateObject(obj: object): void {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) { // Важно: проверяем, что свойство не унаследовано
      Logger.log('Ключ: ' + key + ', Значение: ' + obj[key]);
    }
  }
}

// Пример вызова:
const person: object = { name: 'Алексей', age: 35, profession: 'Инженер' };
iterateObject(person);

Доступ к ключам и значениям свойств объекта

Внутри цикла for...in, переменная key содержит имя свойства (ключ), а obj[key] — его значение.

Особенности перебора прототипных свойств

Цикл for...in перебирает все перечисляемые свойства объекта, включая унаследованные от прототипа. Чтобы избежать этого, необходимо использовать метод hasOwnProperty() для проверки, является ли свойство собственным (не унаследованным):

if (obj.hasOwnProperty(key)) { // Проверка на собственное свойство
  // ...
}

Практические примеры использования foreach в Google Apps Script

Чтение данных из Google Sheets и обработка каждой строки с помощью foreach

/**
 * Читает данные из Google Sheets и обрабатывает каждую строку.
 */
function processSheetData(): void {
  const ss: GoogleAppsScript.Spreadsheet.Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet: GoogleAppsScript.Spreadsheet.Sheet = ss.getActiveSheet();
  const range: GoogleAppsScript.Spreadsheet.Range = sheet.getDataRange();
  const values: any[][] = range.getValues();

  // Пропускаем заголовок (первую строку)
  values.slice(1).forEach(function(row: any[]) {
    const name: string = row[0];
    const email: string = row[1];
    // ... Обработка данных строки
    Logger.log('Имя: ' + name + ', Email: ' + email);
  });
}

Отправка персонализированных email сообщений списку адресов из Google Sheets, используя цикл foreach

/**
 * Отправляет персонализированные email сообщения списку адресов из Google Sheets.
 */
function sendPersonalizedEmails(): void {
  const ss: GoogleAppsScript.Spreadsheet.Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet: GoogleAppsScript.Spreadsheet.Sheet = ss.getActiveSheet();
  const range: GoogleAppsScript.Spreadsheet.Range = sheet.getDataRange();
  const values: any[][] = range.getValues();

  // Пропускаем заголовок (первую строку)
  values.slice(1).forEach(function(row: any[]) {
    const email: string = row[1];
    const name: string = row[0];
    const subject: string = 'Персонализированное сообщение';
    const body: string = 'Здравствуйте, ' + name + '! Спасибо за вашу подписку.';

    MailApp.sendEmail(email, subject, body);
    Logger.log('Отправлено письмо на ' + email);
  });
}

Обработка данных Google Calendar: перебор событий и изменение их свойств

/**
 * Обрабатывает данные Google Calendar: перебирает события и изменяет их свойства.
 */
function processCalendarEvents(): void {
  const calendarId: string = 'primary'; // Или ID нужного календаря
  const calendar: GoogleAppsScript.Calendar.Calendar = CalendarApp.getCalendarById(calendarId);
  const now: Date = new Date();
  const future: Date = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000)); // События на следующую неделю
  const events: GoogleAppsScript.Calendar.CalendarEvent[] = calendar.getEvents(now, future);

  events.forEach(function(event: GoogleAppsScript.Calendar.CalendarEvent) {
    const title: string = event.getTitle();
    // Изменение названия события (пример)
    event.setTitle('[ВАЖНО] ' + title);
    Logger.log('Изменено событие: ' + title);
  });
}

Распространенные ошибки и как их избежать при работе с foreach

Неправильное понимание контекста `this` внутри цикла

Внутри callback-функции forEach, this может ссылаться на глобальный объект (например, window в браузере) или быть undefined, в зависимости от режима strict mode. Чтобы сохранить контекст, можно использовать стрелочные функции (=>) или метод bind().

const myObject: object = {
  name: 'Test',
  items: ['a', 'b', 'c'],
  printItems: function() {
    this.items.forEach(item => {
      Logger.log(this.name + ': ' + item); // this ссылается на myObject
    });
  }
};

myObject.printItems();

Изменение перебираемого массива во время итерации (проблемы и решения)

Как упоминалось ранее, изменение массива в процессе итерации с помощью forEach может привести к непредсказуемым результатам. Используйте map() для создания нового массива или цикл for для более гибкого управления.

Использование `break` и `continue` в циклах foreach (альтернативы)

forEach не поддерживает операторы break и continue. Для прерывания цикла или пропуска итерации используйте цикл for или методы some() и every().

some(): Останавливает итерацию, если callback-функция возвращает true.

every(): Останавливает итерацию, если callback-функция возвращает false.

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

В большинстве случаев, forEach обеспечивает достаточную производительность. Однако, для очень больших массивов или при выполнении сложных операций, цикл for может быть немного быстрее из-за меньших накладных расходов на вызов функций. Выбор зависит от конкретной задачи и приоритетов (читаемость vs. производительность).


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