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

Можно ли создать массив с фиксированной длиной в javascript?

Возможно ли, в Javascript, создать массив, длина которого будет оставаться неизменной?

Например, массив A создается с длиной 2. Впоследствии любая попытка вызвать A.push() или A.pop() или установить значение A [5] не удастся. Длина A. всегда равна 2.

Вот так уже работают типизированные массивы (например, Float32Array). Они имеют фиксированный размер. Но мне нужен способ получить такое же поведение в регулярном массиве.

В моей конкретной ситуации я хотел бы создать массив фиксированной длины, где каждая запись является объектом. Но я все равно хотел бы узнать ответ на общий вопрос.

4b9b3361

Ответ 1

Обновление:

Object.seal (который является частью ES2015) сделает именно это:

var a = new Array(42);
if(Object.seal) { 
  Object.seal(a);
  // now a is a fixed-size array with mutable entries
}

Оригинальный ответ:

Почти. Как было предложено titusfx, вы можете заморозить объект:

var a = new Array(2);

// set values, e.g.
a[0] = { b: 0; }
a[1] = 0;

Object.freeze(a);

a.push(); // error
a.pop(); // error
a[1] = 42; // will be ignored
a[0].b = 42; // still works

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

Для массивов чисел, конечно, есть типизированные массивы.

Object.freeze является частью ES2015, но большинство браузеров, кажется, поддерживают его, включая IE9. Конечно, вы можете протестировать его:

if(Object.freeze) { Object.freeze(obj); }

Ответ 2

Update:

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

Исходный ответ:

Итак, кажется, что ответ на исходный вопрос просто "Нет". Невозможно создать собственный массив JavaScript с фиксированной длиной.

Но вы можете создать объект, который будет вести себя как массив с фиксированной длиной. Следуя рекомендациям в комментариях, я придумал две возможные реализации, как с плюсами, так и с минусами.

Я не понял, какой из двух я буду использовать в своем проекте еще. Я не на 100% доволен. Пожалуйста, дайте мне знать, если у вас есть идеи по их улучшению (я стараюсь сделать эти объекты максимально быстрыми и эффективными, потому что мне нужно много их).

Код для обеих реализаций ниже, а также тесты QUnit, иллюстрирующие использование.

// Version 1
var FixedLengthArrayV1 = function(size) {
    // create real array to store values, hidden from outside by closure
    var arr = new Array(size);
    // for each array entry, create a getter and setter method
    for (var i=0; i<size; i++) {FixedLengthArrayV1.injectArrayGetterSetter(this,arr,i);}
    // define the length property - can't be changed
    Object.defineProperty(this,'length',{enumerable:false,configurable:false,value:size,writable:false});
    // Could seal it at this point to stop any other properties being added... but I think there no need - 'length' won't change, so loops won't change 
    // Object.seal(this);
};
// Helper function for defining getter and setter for the array elements
FixedLengthArrayV1.injectArrayGetterSetter = function(obj,arr,i) {
    Object.defineProperty(obj,i,{enumerable:true,configurable:false,get:function(){return arr[i];},set:function(val){arr[i]=val;}});
};
// Pros:  Can use square bracket syntax for accessing array members, just like a regular array, Can loop just like a regular array
// Cons:  Each entry in each FixedLengthArrayV1 has it own unique getter and setter function - so I'm worried this isn't very scalable - 100 arrays of length 100 means 20,000 accessor functions in memory


// Version 2
var FixedLengthArrayV2 = function(size) {
    // create real array to store values, hidden from outside by closure
    var arr = new Array(size);
    this.get = function(i) {return arr[i];}
    this.set = function(i,val) {
        i = parseInt(i,10);
        if (i>=0 && i<size) {arr[i]=val;}
        return this;
    }
    // Convenient function for looping over the values
    this.each = function(callback) {
        for (var i=0; i<this.length; i++) {callback(arr[i],i);}
    };
    // define the length property - can't be changed
    Object.defineProperty(this,'length',{enumerable:false,configurable:false,value:size,writable:false});
};
// Pros:  each array has a single get and set function to handle getting and setting at any array index - so much fewer functions in memory than V1
// Cons:  Can't use square bracket syntax.  Need to type out get(i) and set(i,val) every time you access any array member - much clumsier syntax, Can't do a normal array loop (need to rely on each() helper function)



// QUnit tests illustrating usage
jQuery(function($){

    test("FixedLengthArray Version 1",function(){

        // create a FixedLengthArrayV2 and set some values
        var a = new FixedLengthArrayV1(2);
        a[0] = 'first';
        a[1] = 'second';

        // Helper function to loop through values and put them into a single string
        var arrayContents = function(arr) {
            var out = '';
            // Can loop through values just like a regular array
            for (var i=0; i<arr.length; i++) {out += (i==0?'':',')+arr[i];}
            return out;
        };

        equal(a.length,2);
        equal(a[0],'first');
        equal(a[1],'second');
        equal(a[2],null);
        equal(arrayContents(a),'first,second');

        // Can set a property called '2' but it doesn't affect length, and won't be looped over
        a[2] = 'third';
        equal(a.length,2);
        equal(a[2],'third');
        equal(arrayContents(a),'first,second');

        // Can't delete an array entry
        delete a[1];
        equal(a.length,2);
        equal(arrayContents(a),'first,second');

        // Can't change the length value
        a.length = 1;
        equal(a.length,2);
        equal(arrayContents(a),'first,second');

        // No native array methods like push are exposed which could let the array change size
        var errorMessage;
        try {a.push('third');} catch (e) {errorMessage = e.message;}
        equal(errorMessage,"Object [object Object] has no method 'push'");
        equal(a.length,2);
        equal(arrayContents(a),'first,second');     

    });

    test("FixedLengthArray Version 2",function(){


        // create a FixedLengthArrayV1 and set some values
        var a = new FixedLengthArrayV2(2);
        a.set(0,'first');
        a.set(1,'second');

        // Helper function to loop through values and put them into a single string
        var arrayContents = function(arr) {
            var out = '';
            // Can't use a normal array loop, need to use 'each' function instead
            arr.each(function(val,i){out += (i==0?'':',')+val;});
            return out;
        };

        equal(a.length,2);
        equal(a.get(0),'first');
        equal(a.get(1),'second');
        equal(a.get(2),null);
        equal(arrayContents(a),'first,second');

        // Can't set array value at index 2
        a.set(2,'third');
        equal(a.length,2);
        equal(a.get(2),null);
        equal(arrayContents(a),'first,second');

        // Can't change the length value
        a.length = 1;
        equal(a.length,2);
        equal(arrayContents(a),'first,second');

        // No native array methods like push are exposed which could let the array change size      
        var errorMessage;
        try {a.push('third');} catch (e) {errorMessage = e.message;}
        equal(errorMessage,"Object [object Object] has no method 'push'");
        equal(a.length,2);
        equal(arrayContents(a),'first,second');     

    });


});

Ответ 3

Текущий ответ - ДА, вы можете. Есть несколько способов сделать это, но некоторые веб-браузеры имеют собственную интерпретацию.

  • Решение, протестированное с помощью FireFox Mozzila Console:

var x = new Array(10).fill(0);
// Output: undefined
Object.freeze(x);
// Output: Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
x.push(11)
// Output: TypeError: can't define array index property past the end of an array with non-writable length
x.pop()
// Output: TypeError: property 9 is non-configurable and can't be deleted [Learn More]
x[0]=10
// Output: 10 // You don't throw an error but you don't modify the array
x
// Output: Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]   

Ответ 4

На самом деле, чтобы создать полностью оптимизированный истинный c-подобный фиксированный массив в js в большинстве современных браузеров (включая IE 11), вы можете использовать: TypedArray или ArrayBuffer, например:

var int16 = new Int16Array(1); // or Float32Array(2)
int16[0] = 42;
console.log(int16[0]); // 42
int16[1] = 44;
console.log(int16[1]); // undefined

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray

Ответ 5

Я написал https://github.com/MatrixAI/js-array-fixed с фиксированным массивом, который является библиотекой, предоставляющей вам массивы фиксированной длины и плотные массивы фиксированной длины (массивы, у которых всегда элементы свернуты влево или вправо).

Он поддерживает множество стандартных операций с массивами, таких как сращивание и срез. Но в будущем можно добавить больше операций.

Концепция push не имеет смысла, вместо этого доступны методы caret*, которые вставляют элемент и выталкивают элементы, которые уже существуют, в пустые слоты.

Ответ 6

Вы можете просто использовать как это.

let myArray = [];
function setItem (array, item, length) {
  array.unshift(item) > length ?  array.pop() : null
}
// Use Like this
setItem(myArray, 'item', 5);

По сути, он будет заполнять элементы в массиве до тех пор, пока длина не станет равной 5, если длина уменьшит значение 5. Это выскочит массив элементов las. Так что он будет поддерживать длину всегда 5.

Ответ 7

Я знаю, что это старый вопрос, но в настоящее время существует модуль node, который выполняет только это: fixed-array