Должны ли отрицательные индексы в массивах JavaScript влиять на длину массива?

85

В javascript я определяю такой массив

var arr = [1,2,3];

также я могу сделать

arr[-1] = 4;

Теперь, если я сделаю

arr = undefined;

Я также теряю ссылку на значение в arr [-1] .

Итак, для меня логически кажется, что arr [-1] также является частью arr .

Но когда я делаю следующее (без установки arr на undefined)

arr.length;

Возвращает 3, а не 4 ;

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

me_digvijay
источник
Почему вы хотите использовать отрицательный индекс? Я не вижу в этом смысла, если я чего-то не упускаю.
elclanrs
11
Вы также можете писать, arr[1.5] = 1и это тоже не влияет на длину. В спецификации языка очень четко указано, что влияет на длину. Возможно, вам это не нравится, но вы должны с этим жить. Либо так, либо создайте свой собственный конкурирующий язык и убедите людей перейти на него.
Raymond Chen
1
@DigvijayYadav: Можно считать недостатком или функцией, как и многие другие вещи в JavaScript. Это связано с тем, что массивы ведут себя как объекты, вы также можете использовать строку, var a = []; a['foo'] = 'baz'но это не значит, что вам следует это делать; это явно противоречит всем условностям.
elclanrs
2
Ребята, главное здесь то, что МАССИВЫ - ЭТО ОБЪЕКТЫ. Нет никакой разницы. Это причина такого поведения, и оно полностью преднамеренно, даже если вам это не нравится. Просто подождите, пока вы не узнаете, что ВСЕ числа представлены как числа с плавающей запятой, даже целые. JavaScript - любопытный язык ...
Тони Р.
2
Вы можете найти подробную информацию об алгоритме, определяющем настройку элементов массива, в спецификации: es5.github.com/#x15.4.5.1. (Особенно шаг 4), а «индекс массива» определяется здесь: es5.github.com /#x15.4 .
Felix Kling

Ответы:

111

Итак, для меня логически кажется, что arr [-1] также является частью arr.

Да, но не так, как вы думаете.

Вы можете присвоить массиву произвольные свойства (как и любой другой объект в JavaScript), что вы и делаете, когда «индексируете» массив в -1и присваиваете значение. Поскольку это не член массива, а просто произвольное свойство, вам не следует ожидать lengthрассмотрения этого свойства.

Другими словами, следующий код делает то же самое:

var arr = [1, 2, 3];

​arr.cookies = 4;

alert(arr.length) // 3;
Эндрю Уитакер
источник
31
Хорошо, это означает, что отрицательные индексы на самом деле не действуют как реальные индексы.
me_digvijay
4
Да, это просто произвольные свойства массива.
Эндрю Уитакер
Но что, если я хочу использовать положительные индексы в качестве свойств, отличных от индексов, и не хочу, чтобы они вносили вклад в длину массива?
me_digvijay
7
Тогда не используйте массив, используйте простой старый объект. Но в этом случае вы потеряете встроенное lengthимущество.
Эндрю Уитакер
2
@Digvijay: консоль показывает только свойства с положительными целочисленными именами, потому что (я предполагаю) она просто выполняет обычный forцикл от 0до .length. Сделайте, console.dir(arr)чтобы увидеть объект целиком. Кроме того, использование скобок для доступа к массивам не является характеристикой массивов, это неотъемлемая характеристика объектов ( var obj = {foo: 'bar'}; obj.foo or obj['foo']).
Felix Kling
25

lengthСвойство возвращает число на единицу больше , чем самый высокий назначенный «индекс», где массив «индексы» являются целыми числами больше или равно нулю. Обратите внимание, что JS допускает "разреженные" массивы:

var someArray = [];
someArray[10] = "whatever";
console.log(someArray.length); // "11"

Конечно, если элементов нет, то lengthесть 0. Также обратите внимание, что lengthне обновляется, если вы используете deleteдля удаления самого высокого элемента.

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

someArray[-1] = "A property";
someArray[3.1415] = "Vaguely Pi";
someArray["test"] = "Whatever";

Обратите внимание, что за кулисами JS преобразует имена свойств в строки, даже если вы указываете число вроде -1. (Положительные целочисленные индексы также становятся строками, если на то пошло.)

Методы массивов, например .pop(), .slice()и т. Д., Работают только с целочисленными «индексами», равными нулю или выше, а не с другими свойствами, поэтому lengthединообразны в этом отношении.

ннннн
источник
Спасибо, ваш ответ очень помог. Также я попробовал метод сращивания с отрицательным индексом, я обнаружил, что если я использую -1, он переходит к последнему элементу массива, для -2 переходит ко второму последнему элементу и так далее.
me_digvijay
+1. Длина не говорит вам, сколько там элементов или сколько у них свойств. Это просто наивысший индекс +1. Например, данный var a = new Array(9), aимеет длину 9 и совсем не содержит элементов.
RobG
1
@DigvijayYadav - Да, Array.slice()метод должен это делать. String.slice()Метод делает то же самое.
nnnnnn
7

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

var array = [];

array[0] = "Foo";
array[1] = "Bar";

// Result: ["Foo", "Bar"]
// Length: 2

Это не тот случай, когда вы добавляете неиндексные значения (не 0-9 +):

var array = [];

array[0]  = "Foo";
array[1]  = "Bar";
array[-1] = "Fizzbuzz"; // Not a proper array index - kill it

// Result: ["Foo", "Bar"]
// Length: 2

Значения помещаются в массив только тогда, когда вы играете по правилам. Если вы этого не сделаете, они не будут приняты. Однако они принимаются в самом объекте Array, что относится практически ко всему в JavaScript. Несмотря на то, что ["Foo", "Bar"]это единственные значения в нашем массиве, мы все равно можем получить доступ "Fizzbuzz":

array[-1]; // "Fizzbuzz"

Но еще раз обратите внимание, что это не часть значений массива, поскольку его «индекс» недействителен. Вместо этого он был добавлен в массив как еще один член. Таким же образом мы могли получить доступ к другим членам массива:

array["pop"]; // function pop() { [native code] }

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

for (var prop in array) 
    console.log(prop, array[prop]);

Из чего следует следующее:

 0 Foo
 1 Bar
-1 Fizzbuzz

Так опять же , это на на объекте , но не в в массиве .

Замечательный вопрос! Наверняка заставил меня задуматься.

Sampson
источник
1
+1 Даже в спецификации указано, что свойства с положительным числовым именем свойства называются элементами массива (а любое другое свойство - нет): es5.github.com/#x15.4 .
Felix Kling
Результат нет: ["Foo", "Bar"]но ["Foo", "Bar", -1: "Fizzbuzz"]проверьте скрипку здесь . Как вы и писали, оно становится свойством с ключом -1, но определенно видно на выходе. В противном случае ваш ответ будет красивым и полным. Если вы измените, я снова проголосую за, мой голос против был резким, но не могу его удалить; время истекло.
Уилт
5

Если вы действительно хотите, чтобы в длину были включены отрицательные индексы и другие индексы, создайте функциональность самостоятельно:

function getExtendedArray() {
    var a = [];

    Object.defineProperty(a, 'totalLength', { 
        get : function() { return Object.keys(a).length; }
    });

    return a;
}

Тогда например:

var myArray = getExtendedArray();
console.log(myArray.totalLength); // 0
myArray.push("asdf");
myArray[-2] = "some string";
myArray[1.7777] = "other string";
console.log(myArray.totalLength); // 3
Дэвид Шеррет
источник
И если вы хотите, чтобы длина включала только пронумерованные индексы / свойства, вы можете добавить оператор if для ключа - if(Number(key) != NaN) length++;если вы хотите отфильтровать плавающие, добавьте && key % 1 == 0к условию
Боннев
4

Массив - это просто особый вид объекта в JS. Есть специальный синтаксис, и это хорошо, потому что позволяет эффективно с ними работать. Представьте, насколько утомительно было бы писать таким образом литерал массива:

const array = {0: 'foo', 1: 'bar', 2: 'baz'} //etc...

Но тем не менее они остаются объектами . Итак, если вы это сделаете:

const myArray = [42, 'foo', 'bar', 'baz'];

myArray[-1] = 'I am not here';
myArray['whatever'] = 'Hello World';

Эти нецелочисленные свойства будут прикреплены к объекту (какому массиву), но они недоступны для некоторых собственных методов, таких как Array.length.

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

Согласно спецификациям , собственный lengthметод - как getter ( exampleArray.length) - будет проверять все неотрицательные целые числа меньше 2 32 » ключей, получать наибольший из них и возвращать его числовое значение + 1.

Как setter ( exampleArray.length = 42), он либо создаст несколько пустых слотов для `` отсутствующих '' индексов, если заданная длина больше, чем фактическое количество неотрицательных целочисленных ключей, либо удалит все неотрицательные целочисленные ключи (и их значения), которые не превышают заданная длина.

Псевдо-реализация:

const myArray = {
  '-1': 'I am not here', // I have to use apostrophes to avoid syntax error
  0: 42,
  1: 'foo',
  2: 'bar',
  3: 'baz',
  whatever: 'Hello World',

  get myLength() {
    let highestIndex = -1;
    for (let key in this) {
      let toInt = parseInt(key, 10);
      if (!Number.isNaN(toInt) && toInt > -1) {
        // If you're not an integer, that's not your business! Back off!
        if (toInt > highestIndex) highestIndex = toInt;
      }
    }
    return highestIndex + 1;
  },
  set myLength(newLength) {
    /* This setter would either:
      1) create some empty slots for 'missing' indices if the given length is greater than the actual number of non-negative-integer keys
      2) delete all non-negative-integer keys (and their values) which are not greater then the given length.
    */
  }
}

console.log(myArray.myLength); // 4

myArray[9] = 'foobar';

console.log(myArray.myLength); // 10

HynekS
источник
3

Массивы в JavaScript на самом деле являются объектами. Их просто прототипируют из конструктора Array.

Индексы массивов на самом деле являются ключами в хэш-карте, и все ключи преобразуются в строки. Вы можете создать любой ключ (например, «-1»), но методы в массиве настроены так, чтобы действовать как массив. Так что lengthэто не размер объекта, он скорее гарантированно больше, чем наибольший целочисленный индекс. Точно так же при печати arrбудут перечислены только значения с целыми ключами> = 0.

Больше информации здесь.

Тони Р
источник
точно. или мы могли бы начать задаваться вопросом, почему array ['foo'] не является допустимым индексом - javascript позволяет вам это делать, но не означает, что определение того, что массив (и его элементы) изменяются с одного языка на другой. работает по назначению.
tw airball
На самом деле «массивов» в JS не существует. Но в основе JS лежат «ассоциативные массивы», также известные как «объекты». Так что создание объекта Array, имитирующего обычный массив, не было большим шагом вперед.
Tony R
1
Есть одно особое различие между массивами и простыми объектами: свойство саморегулирующейся длины.
RobG
1

Согласно MDN:

Значение свойства length представляет собой целое число с положительным знаком и значением меньше 2 в степени 32 (232).

В Javascript вы можете установить свойство для любого создаваемого вами объекта.

var array = new Array();
array = [1,2,3];
array["boom"] = "pow";

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

array[-1] = "property does not add to array length";

Вот почему длина не отражает это, а цикл for..in показывает это.

Ktilcu
источник
-1

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

Для итерации по массиву вы можете использовать

for(var index in myArray) {
  document.write( index + " : " + myArray[index] + "<br />");
}
Ашан
источник