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

var arr = [[1,2], [3,4], [5,6]];
for (var i=0; i < arr.length; i++) {
    for (var j=0; j < arr[i].length; j++) {
        console.log(arr[i][j]);
    }
}

Это выводит 1 2 3 4 5 6, что я и ожидал. Однако, если я добавлю числа в конец внешнего массива:

var arr = [[1,2], [3,4], [5,6], 7, 8];
for (var i=0; i < arr.length; i++) {
    for (var j=0; j < arr[i].length; j++) {
        console.log(arr[i][j]);
    }
}

Я все еще получаю тот же результат 1 2 3 4 5 6? ? Я запутался, почему 7 и 8 не попадают в цикл. Интересно, если я вместо этого использую строки:

var arr = [["a","b"], ["c","d"], "y", "z"];
for (var i=0; i < arr.length; i++) {
    for (var j=0; j < arr[i].length; j++) {
        console.log(arr[i][j]);
    }
}

Вывод - это b c d y z, что я и ожидал. Почему он ведет себя по-разному для строк?

4
quietplace 24 Фев 2018 в 03:47

5 ответов

Лучший ответ

Вот как все выглядит в современном Javascript.

Что касается циклов, все значения можно разделить на «повторяемые» и «не повторяемые». Итерируемыми являются значения, которые вы можете хорошо ... итерировать - с помощью цикла for..of.

for (let item of someIterableThing)
    // use item

(Вы не используете пустые for циклы - for(var i...i < length) - для итерации, потому что не каждая итерация имеет length и индексы.)

И наоборот, если вы сделаете for...of с не повторяемой вещью, вы получите ошибку.

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

[ [1,2], [3,4], "foobar" ]

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

[ [1,2], [3,4], 999]

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

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

 let isIterable = x => x && x[Symbol.iterator]

(см. специальную тему).

Затем вы можете безопасно использовать вложенный цикл:

for (let item of array)
    if (isIterable(item))
        for (let subItem of item)
            console.log(subItem)
    else
        console.log(item)

Как примечание, есть много устаревшей информации о Javascript в сети. Язык развивается, и то, что было хорошо 5 лет назад, в наши дни считается плохой практикой. К сожалению, большинство учебных пособий, книг и учителей не успевают и продолжают продвигать старые методы, такие как использование пустых for циклов.

(Поскольку люди спрашивают, почему именно голые циклы for плохи, рассмотрим этот пример:

У вас есть массив text, содержащий строки и несколько функций, которые обрабатывают этот массив. Программист А пишет эти функции старомодным способом:

for (var i = 0; i < text.length; i++)  
     do_something_with(text[i]) // ;(

Программист Б пишет их по-современному:

for (let str of text)  
     do_something_with(str) // :)

Теперь text становится все больше и больше и больше не умещается в памяти. Поэтому системный архитектор решил заменить его потоковым файловым объектом, который одновременно выдает только одну строку. Программист А теперь должен переписать все свои 100 функций для адаптации к новому интерфейсу:

  for (var file = TextFile; !file.eof(); file.getNext())
      do_something_with(file.currentLine)

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

Программист Б просто наслаждается своим отпуском.)

1
georg 24 Фев 2018 в 10:10

Как уже упоминали другие, ваш внутренний цикл предназначен для итерации массивов, найденных на верхнем уровне (не вложенный массив). Предполагается, что все элементы в массиве верхнего уровня будут вложенными массивами, что не так. (Вы должны убедиться, что у вас есть массив, прежде чем пытаться выполнить его итерацию.) Поскольку 7 и 8 на верхнем уровне не являются массивами, arr[i].length возвращает {{X3} } для чисел, но строки являются объектами типа «массив» и имеют свойство length. Строка "y" имеет length 1, поэтому внутренний цикл работает, потому что он начинается с нуля и получает символ в нулевой позиции в строке «массив» {{X8} }, который "y".

Но это хорошая причина не использовать традиционные циклы for с массивами, когда у нас теперь есть Array.forEach() , что устраняет необходимость ручного управления индексами и позволяет получить прямой доступ к перечисляемому значению не заботясь об индексах.

var arr = [[1,2], [3,4], [5,6], 7, 8];
var output = "";

// Enumerate the top-level array:
arr.forEach(function(value){
  // Check to see if the item is an array
  if(value instanceof Array){
    // If so, enuerate that nested array
    value.forEach(function(nestedValue){
      // Add the nested array's value to the output
      output += nestedValue + " " ;
    });
  } else {
    // Item is not an array, just add its value to the output
    output += value + " ";
  }
});

console.log(output);

Да, и, кстати, я понимаю, что это не то, о чем вы спрашивали, а просто как к сведению, вот способ получить все значения без каких-либо циклов:

console.log([[1,2], [3,4], [5,6], 7, 8].toString().split(",").join(" "));
2
Scott Marcus 24 Фев 2018 в 01:10

String, Array, TypedArray, Map и Set являются встроенные итерируемые, поскольку каждый из их объектов-прототипов реализует метод @@iterator. Хотя Number не повторяется:

const iterate = v => {
  for (var i = 0; i < v.length; i++) console.log(v[i])
}

iterate([1, 'two', 777]) // iterates by element
iterate('abc') // iterates by symbol
iterate(123) // does not iterate
2
Kosh 24 Фев 2018 в 01:25

Почему он ведет себя по-разному для строк?

Потому что строки обрабатываются как массив символов.

Строка JavaScript хранит серию символов, таких как «Джон Доу». Каждый символ может быть доступен с помощью индекса массива, а также имеет метод length для получения размера строки. Следовательно, почему ваш "y", "z" работает, но не 7, 8, поскольку они не являются массивом.

1
Azizur Rahman 24 Фев 2018 в 01:04

У вас есть двойная петля. Когда вы делаете оператор console.log(arr[i][j]);, вы сначала пытаетесь перейти к индексу i массива arr в этой части arr[i].

Затем вы продолжаете вызывать индекс значения в массиве с помощью [j]. Потому что 7 и 8 - это просто числа, а не массивы, которые они не регистрируют.

Это причина, почему письма регистрируются:

 var arr = [["a","b"], ["c","d"], "y", "z"];
 for (var i=0; i < arr.length; i++) {
   for (var j=0; j < arr[i].length; j++) {
/* 		console.log(arr[i][j]);  */
  }
}

var foo = "a";
var bar = foo[0];

console.log(bar);

Строки ведут себя как массивы символов, поэтому вы получаете букву, возвращающую себя, когда вы вызываете foo [0], она возвращает a.

1
Willem van der Veen 24 Фев 2018 в 01:02