代码之家  ›  专栏  ›  技术社区  ›  Shuai Li

JavaScript支持固定长度数组吗?[重复]

  •  1
  • Shuai Li  · 技术社区  · 7 年前

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

    例如,数组 A 创建的长度为2。随后,任何试图拨打 A.push() A.pop() ,或设置 A[5] 这将失败。 A.length 永远都是2。

    这是类型化数组(例如 Float32Array )已经开始工作了。它们有固定的尺寸。但我想要一种方法,在常规数组上获得相同的行为。

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

    0 回复  |  直到 6 年前
        1
  •  40
  •   tim-we    6 年前

    更新:

    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
    

    但是,无法更改冻结对象的值。 如果您有一个对象数组,这可能不是问题,因为您仍然可以 更改对象的值。

    对于数字数组,当然有 typed arrays .

    Object.freeze ES2015 but most browsers seem to support it, including IE9 .你当然可以测试它:

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

        2
  •  6
  •   Daniel Howard    9 年前

    更新:

    公认的答案表明了这个问题是如何解决的 可以 现在可以使用 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
  •  6
  •   Micheal Kris    9 年前

    实际上,要在大多数现代浏览器(包括IE 11)上用js创建一个完全优化的真正的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

        4
  •  4
  •   Rajeev    7 年前

    你可以这样简单地使用。

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

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

        5
  •  1
  •   titusfx    9 年前

    目前的答案是可以的。有几种方法可以做到这一点,但有些网络浏览器有自己的“解释”。

    1. 通过FireFox Mozzila控制台测试的解决方案 :

    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 ]   

    重要的是要注意,如果数组是对象,则需要进行深度冻结。deepfreeze的代码是 here .

    1. 包装数组的类 (最好不要抛出异常)

    2. 对于ES2015,代码应该适用于以下解决方案,但事实并非如此:

    var x = new Array(10).fill(0);
    Object.freeze( x.length );
    x.push(3);
    console.log(x);
    看看这个 page in the section Note
        6
  •  1
  •   nullspace    5 年前
    1. 使用 new Array

    但是,创建的数组中填充了 undefined .因此,使其不可替代。你可以把它装满 null 0 而是价值观。

    new Array(100).fill(null).map(() => ...);
    
    1. 使用 Array.from 方法
    Array.from({ length: n }, (_,i) => i) 
    
        7
  •  0
  •   CMCDragonkai    8 年前

    我已经写了一个数组 https://github.com/MatrixAI/js-array-fixed 它是一个库,为您提供固定长度数组和固定长度密集数组(数组的元素总是向左折叠或向右折叠)。

    它支持许多标准阵列操作,例如拼接和切片。但未来还可以增加更多的业务。

    概念 push 没有道理,反而有 caret* 方法,用于插入元素并将已存在的元素推出到空插槽中。

        8
  •  0
  •   Pulkit Aggarwal    6 年前

    我们可以用闭包来解决这类问题。我们只是固定了数组的大小,然后从一个函数返回一个函数。

        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);
        9
  •  0
  •   JSkyS    6 年前

    您可以实现一个具有容量的类。假设您希望在推入阵列时长度保持在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)
        10
  •  0
  •   dave110022    4 年前

    大堆如果阵列为空,pop已经失败。 如果推送会违反固定大小,则希望推送失败,所以不要使用数组。只需使用一个函数即可:

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

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

    function arrayPush2(array,size,value){
        if(array.length==size){
            array.splice(0,1);
        }
        array.push(value);
    }
    
        11
  •  -1
  •   andreiashu    9 年前

    我知道这是一个老问题,但现在有一个节点模块可以实现这个功能,叫做 fixed-array

        12
  •  -1
  •   Fatih Kilic    5 年前

    使用shift和push时 在固定长度数组中,必须在长度控制之前或之后进行选择,才能在添加或拒绝新项之前从数组的开头或结尾删除项。。我最快的解决方案就是这样。 如果 使用just键访问数组 可以很容易地控制和预期固定大小的行为。对象和数组都可以使用。