JavaScript中是否可以创建定长数组?

57

在Javascript中,是否可以创建一个长度保持不变的数组?

例如,创建一个长度为2的数组A。随后,任何试图调用A.push()或A.pop(),或设置A [5]的值都将失败。A.length将始终为2。

这就是类型化数组(例如Float32Array)已经实现的方式。它们具有固定的大小。但我想知道在常规数组上获得相同行为的方法。

对于我的特定情况,我想创建一个固定长度的数组,其中每个条目都是一个对象。但我仍然想了解一般问题的答案。


3
不能使用本地数组,但可以创建一个类似数组的对象。 - Denys Séguret
4
你可以自己实现。只需将一个数组对象封装起来,但不公开pushpop或其他修改方法。如果希望保留内部封装的数组不可访问,可以使用闭包。 - salezica
2
为什么一定要使用数组?只需使用一个 object,然后 freeze 它,这样属性就无法被修改。请注意,如果您有一个对象的对象,则冻结仅适用于您冻结的对象,而不适用于任何嵌套对象。 - Andy
@uʍopǝpısdn - 是的 - 好的 - 我想我会做类似的事情。感谢您的建议。 - Daniel Howard
谢谢你的链接,@Andy。那篇文章中有一些非常有趣的想法。 - Daniel Howard
显示剩余2条评论
15个回答

57

更新:

Object.seal(它是ES2015的一部分)可以做到这一点:

// create array with 42 empty slots
let a = new Array(42);

if(Object.seal) {
  // fill array with some value because
  // empty slots can not be changed after calling Object.seal
  a.fill(undefined);

  Object.seal(a);
  // now a is a fixed-size array with mutable entries
}

翻译:

几乎正确。正如titusfx所建议的那样,您可以冻结该对象:

let 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.freezeES2015的一部分,但大多数浏览器都支持它,包括IE9。您当然可以进行特性测试: if(Object.freeze) { Object.freeze(obj); }

1
我认为你需要填充你的数组:https://repl.it/@baruchiro/SphericalGrizzledTask - baruchiro
1
谢谢你提出这个问题,我之前并不知道那些空槽位和未定义的值是不同的。 - tim-we

9

实际上,在大多数现代浏览器(包括IE 11)中创建一个完全优化的类似于C语言的固定数组,您可以使用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


如果人们需要一个所有条目都具有相同数据类型的数组,这可能对他们有所帮助。原始问题已经提到了Float32Array作为此类数组的示例。但是,当数组的条目可能不是相同类型时,我认为它并没有什么帮助。 - Daniel Howard

8
你可以像这样简单地使用它。
let myArray = [];
function setItem (array, item, length) {
  array.unshift(item) > length ?  array.pop() : null
}
// Use Like this
setItem(myArray, 'item', 5);

基本上,它会填充数组中的项目,直到长度达到5。如果长度大于5,则它将弹出最后一个数组项。因此,它始终保持长度为5。


7

更新:

接受的答案展示了如何使用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's 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's 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
希望您能明确得出结论:不要那样做。 - Denys Séguret

5
  1. 使用new Array构造函数

然而,创建的数组被填充了undefined。因此使其不可迭代。相反,您可以用null0值来填充它。

new Array(100).fill(null).map(() => ...);
  1. 使用 Array.from 方法
Array.from({ length: n }, (_,i) => i) 

0

如果数组为空,Array.pop已经失败了。 如果push操作会违反固定大小,您希望它失败-因此不要使用Array.push,而是使用一个函数:

function arrayPush(array,size,value){
    if(array.length==size) return false;
    else {
       array.push(value);
       return true;
    }
}

我使用一种不同类型的固定长度数组来保存类似最近文件的东西。在这种情况下,您可以继续推送,而数组将仅存储最后固定数量的项目。请记住,Array.push会添加到数组的末尾,因此要推送另一个项目,您需要使用splice(0,1)来删除数组的第一个项目。

function arrayPush2(array,size,value){
    if(array.length==size){
        array.splice(0,1);
    }
    array.push(value);
}

0
我们可以使用闭包来解决这种类型的问题。我们只是固定了数组大小并从函数中返回一个函数。

    function setArraySize(size){
   return function(arr, val) {
      if(arr.length == size) {
          return arr;    
       } 
   arr.push(val);
   return arr;
   }
}
let arr = [];
let sizeArr = setArraySize(5); // fixed value for fixed array size.
sizeArr(arr, 1);
sizeArr(arr, 2);
sizeArr(arr, 3);
sizeArr(arr, 4);
sizeArr(arr, 5);
sizeArr(arr, 6);
console.log('arr value', arr);


0

我已经编写了一个名为 https://github.com/MatrixAI/js-array-fixed 的 array-fixed 库,它提供了固定长度数组和固定长度密集数组(始终将其元素折叠到左侧或右侧)。

它支持许多标准的数组操作,例如 splice 和 slice。但是在未来可以添加更多操作。

push 的概念并不合适,相反有 caret* 方法可用于插入元素并将已存在的元素推出到空槽中。


0

let a = new Array(42)

默认情况下,值将为undefined。例如,a[0]a[41]的值将为undefined


0

你可以实现一个带有容量的类。比如说,当向数组中添加元素时,你想让长度保持在5。如果运行代码片段,你会发现6没有被添加到数组中,因为容量已经满了。祝好。

class capArray{
    constructor(capacity){
    this.capacity = capacity;
    this.arr = [];
}

}

capArray.prototype.push = function(val){
    if(this.arr.length < this.capacity) {
this.arr.push(val);
}
}

var newArray = new capArray(5);
newArray.push(1)
newArray.push(2)
newArray.push(3)
newArray.push(4)
newArray.push(5)
newArray.push(6)
console.log(newArray)
console.log(newArray.arr)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接