2008-12-14 2 views
12

Есть ли способ создать подобный массиву объект в 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 без каких-либо методов называют. Я понимаю от this question, что оператор [] перегружен в случае массивов, и это объясняет это поведение. Есть ли способ с чистым javascript для дублирования этого поведения или недостаточно гибкий для этого язык?

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

ответ

3

Теперь у нас есть ECMAScript 2 015 (ECMA-262 6-е издание; ES6), мы имеем proxy objects, и они позволяют реализовать 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's 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 и геттерами, это позволяет получить length в постоянное время (очевидно, это все еще зависит от того, что доступ к основным свойствам в VM является постоянным временем), и единственный случай, когда он обеспечивает Непостоянная производительность по времени - это не реализованный случай, когда свойства newLen - oldLen удалены (и удаление в большинстве виртуальных машин происходит медленно).

-2

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

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

+0

Это скорее теоретический вопрос о границах javascript, чем что-либо практическое = P. Я думаю, что мой ответ заключается в том, что вы не можете этого сделать. – Claudiu 2008-12-14 01:36:39

1

В основном вам не нужно заранее определенный индекс-размер для массивов в 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] 
+0

BTW, чтобы ответить на ваш конкретный вопрос больше; Пользовательский объект, который лучше отслеживает индексы массива, если они важны для вашей причины, возможно, сохранит для этого внутренний метод массива и метода getter/setter. Объект может заполнить пустые индексы «null», чтобы лучше представить array.length. – finpingvin 2008-12-14 01:43:51

22

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

Если вы хотите вернуть вычисленные значения в операторе [], вы не сможете сделать это в 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); 
} 
+0

спасибо - это тот ответ, который я хотел получить. – Claudiu 2008-12-14 02:00:49

+1

Начиная с JavaScript 1.8.5 вы * можете * вычислить свойства. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty – mpen 2013-02-12 16:31:35

+0

@mpen и ES5, для тех, кто заботится о спецификациях и о том, что делают другие браузеры (учитывая номера версий JS в основном просто кодируйте релизы SpiderMonkey!). – gsnedders 2016-06-10 22:46:09

1

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

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

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

+0

Я считаю, что ваш код на самом деле точно соответствует моему коду = P. [] является литералом для «new Array()», а sup [0] и sup ['0'] ведут себя одинаково. – Claudiu 2008-12-14 01:59:53

+0

О, извините, я не видел, что у вас не было начального индекса. – Claudiu 2008-12-14 02:07:45

+0

Массивы и хэш-таблицы (и, следовательно, объекты) все тесно связаны в Javascript, и между ними не так много различий. Массив со строковыми клавишами ведет себя как Hashtable, поэтому в приведенном выше примере он не будет заполнять значения со значениями от 1 до 4. – 2008-12-16 21:56:06

12

Да, вы можете унаследовать массив в объект 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 'How To Subclass The JavaScript Array Object.Позже выяснилось, что его обходной способ не был безопасным, поскольку некоторые блокирующие всплывающие окна предотвратят это.

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

1

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

3

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

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 петлями, как будто это было . свойство Жаль, что это не способ установить property attributes (это одна вещь, которую я действительно хотел бы сделать)

1

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

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