Подтвердить что ты не робот

Внедрить поведение типа Array в JavaScript без использования массива

Есть ли способ создать подобный массиву объект в JavaScript, не используя встроенный массив? Я специально занимаюсь таким поведением:

var sup = new Array(5);
//sup.length here is 0
sup[0] = 'z3ero';
//sup.length here is 1
sup[1] = 'o3ne';
//sup.length here is 2
sup[4] = 'f3our';        
//sup.length here is 5

Особое поведение, которое я здесь рассматриваю, заключается в том, что sup.length изменяется без вызова каких-либо методов. Я понимаю из этого вопроса что оператор [] перегружен в случае массивов, и это объясняет это поведение. Есть ли способ с чистым javascript для дублирования этого поведения или недостаточно гибкий для этого язык?

В соответствии с Документы Mozilla значения, возвращаемые регулярным выражением, также делают фанки с этим индексом. Возможно ли это с помощью простого javascript?

4b9b3361

Ответ 1

Теперь у нас есть ECMAScript 2015 (ECMA-262 6th Edition, ES6), прокси-объекты, и они позволяют нам реализовать Array в самом языке, что-то вроде:

function FakeArray() {
  const target = {};

  Object.defineProperties(target, {
    "length": {
      value: 0,
      writable: true
    },
    [Symbol.iterator]: {
      // http://www.ecma-international.org/ecma-262/6.0/#[email protected]@iterator
      value: () => {
        let index = 0;

        return {
          next: () => ({
            done: index === target.length,
            value: target[index++]
          })
        };
      }
    }
  });

  const isArrayIndex = function(p) {
    /* an array index is a property such that
       ToString(ToUint32(p)) === p and ToUint(p) !== 2^32 - 1 */
    const uint = p >>> 0;
    const s = uint + "";
    return p === s && uint !== 0xffffffff;
  };

  const p = new Proxy(target, {
    set: function(target, property, value, receiver) {
      // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-exotic-objects-defineownproperty-p-desc
      if (property === "length") {
        // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-arraysetlength
        const newLen = value >>> 0;
        const numberLen = +value;
        if (newLen !== numberLen) {
          throw RangeError();
        }
        const oldLen = target.length;
        if (newLen >= oldLen) {
          target.length = newLen;
          return true;
        } else {
          // this case gets more complex, so it left as an exercise to the reader
          return false; // should be changed when implemented!
        }
      } else if (isArrayIndex(property)) {
        const oldLenDesc = Object.getOwnPropertyDescriptor(target, "length");
        const oldLen = oldLenDesc.value;
        const index = property >>> 0;
        if (index > oldLen && oldLenDesc.writable === false) {
          return false;
        }
        target[property] = value;
        if (index > oldLen) {
          target.length = index + 1;
        }
        return true;
      } else {
        target.property = value;
        return true;
      }
    }
  });

  return p;
}

Я не могу гарантировать, что это на самом деле абсолютно правильно, и он не обрабатывает случай, когда вы изменяете длину, меньшую, чем ее предыдущее значение (поведение немного сложное, чтобы получить право, грубо оно удаляет свойства, поэтому что имеет место инвариант свойства length), но он дает приблизительный контур того, как вы можете его реализовать. Он также не имитирует поведение [[Call]] и [[Construct]] на Array, что является еще одним вещью, которую вы не могли сделать до ES6 - не было возможности иметь дивергентное поведение между ними внутри ES, хотя это не сложно.

Это реализует свойство length так же, как спецификация определяет его как работающий: он перехватывает присвоения свойствам объекта и изменяет свойство length, если это "индекс массива".

В отличие от того, что можно делать с ES5 и getters, это позволяет получить length в постоянное время (очевидно, это все еще зависит от того, что доступ к свойствам в базе данных является постоянным временем), и единственный случай, когда это обеспечивает не постоянную производительность по времени - это не реализованный случай, когда свойства newLen - oldLen удаляются (а удаление в большинстве виртуальных машин происходит медленно).

Ответ 2

Оператор

[] - это собственный способ доступа к свойствам объекта. Он недоступен в языке для переопределения, чтобы изменить его поведение.

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

MyClass.prototype.getItem = function(index)
{
    return {
        name: 'Item' + index,
        value: 2 * index
    };
}

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

MyClass.prototype.addItem = function(item)
{
    // Will add "item" in "this" as if it was a native array
    // it will then be accessible using the [] operator 
    Array.prototype.push.call(this, item);
}

Ответ 3

Да, вы можете легко подклассифицировать массив в объект arraylike в JavaScript:

var ArrayLike = function() {};
ArrayLike.prototype = [];
ArrayLike.prototype.shuffle = // ... and so on ...

Затем вы можете создавать новые объекты типа:

var cards = new Arraylike;
cards.push('ace of spades', 'two of spades', 'three of spades', ... 
cards.shuffle();

К сожалению, это не работает в MSIE. Он не отслеживает свойство length. Что довольно дефлирует все это.

Проблема более подробно о Dean Edwards 'Как подкласс объекта JavaScript Array. Позже выяснилось, что его обходной способ был небезопасным, поскольку некоторые блокирующие всплывающие окна предотвратят его.

Обновление: Стоит упомянуть Юрия "kangax" Зайцева абсолютно эпическое сообщение по этому вопросу. Это в значительной степени охватывает все аспекты этой проблемы.

Ответ 4

Это то, что вы ищете?

Thing = function() {};
Thing.prototype.__defineGetter__('length', function() {
    var count = 0;
    for(property in this) count++;
    return count - 1; // don't count 'length' itself!
});

instance = new Thing;
console.log(instance.length); // => 0
instance[0] = {};
console.log(instance.length); // => 1
instance[1] = {};
instance[2] = {};
console.log(instance.length); // => 3
instance[5] = {};
instance.property = {};
instance.property.property = {}; // this shouldn't count
console.log(instance.length); // => 5

Единственный недостаток заключается в том, что "длина" будет повторяться в циклах for.in, как если бы это было свойство. Жаль, что нет атрибута атрибутов свойств (это одна вещь, которую я действительно хотел бы сделать).

Ответ 5

В основном вам не нужен предопределенный размер индекса для массивов в javascript, вы можете просто сделать:

var sup = []; //Shorthand for an empty array
//sup.length is 0
sup.push(1); //Adds an item to the array (You don't need to keep track of index-sizes)
//sup.length is 1
sup.push(2);
//sup.length is 2
sup.push(4);
//sup.length is 3
//sup is [1, 2, 4]

Ответ 6

Если вы беспокоитесь о производительности с вашим разреженным массивом (хотя, вероятно, вам этого не должно быть), и вы хотите, чтобы структура была до тех пор, пока элементы, которые вы ей передали, вы могли бы сделать это:

var sup = [];
sup['0'] = 'z3ero';
sup['1'] = 'o3ne';
sup['4'] = 'f3our';        
//sup now contains 3 entries

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

Ответ 7

Ответ: на данный момент нет способа. Поведение массива определено в ECMA-262 как поведение этого способа и имеет явные алгоритмы для того, как справляться с получением и настройкой свойств массив (а не общих свойств объекта). Это несколько смущает меня = (.

Ответ 8

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

Array.prototype.mylength = function() {
    var result = 0;
    for (var i = 0; i < this.length; i++) {
        if (this[i] !== undefined) {
            result++;
        }
    }
    return result;
}

Ответ 9

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

Но почему бы просто не использовать push/pop?