62

Is it possible, in Javascript, to create an array whose length is guaranteed to remain the same?

For example, the array A is created with length 2. Subsequently, any attempt to call A.push() or A.pop(), or set the value of A[5] will fail. A.length will always be 2.

This is the way that typed arrays (eg Float32Array) already work. They have fixed size. But I want a way to get the same behaviour on a regular Array.

For my specific situation, I would like to create a fixed-length array where each entry is an object. But I would still like to know the answer to the general question.

7
  • 3
    With a native array, no way. But you can create an array-like object. Commented Feb 24, 2014 at 13:16
  • 4
    You can implement it yourself. Just wrap an array object but expose no push, pop or other modification methods. If you want to keep the internal wrapped array inaccessible, you can use a closure Commented Feb 24, 2014 at 13:16
  • 2
    Why does it have to be an array? Just use an object and then freeze it so the properties can't be modified. Note the if you have an object of objects, the freezing only works on the object you freeze and not any of it's nested objects. Commented Feb 24, 2014 at 13:34
  • @Andy Good idea, but I really want it to be an array because in this case the order of the elements is critical. Array elements are ordered, and object elements aren't. Commented Feb 24, 2014 at 16:39
  • @uʍopǝpısdn - yes - ok - I think I will do something like that. Thanks for the suggestion. Commented Feb 24, 2014 at 16:41

14 Answers 14

63

Update:

Object.seal (which is part of ES2015) will do just that:

// 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
}

Original Answer:

Almost. As was suggested by titusfx you can freeze the object:

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

However you are unable to change the values of a freezed object. If you have an array of objects this may not be a problem since you can still change the values of the objects.

For arrays of numbers there are of course typed arrays.

Object.freeze is part of ES2015 but most browsers seem to support it, including IE9. You could of course feature-test it:

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

Sign up to request clarification or add additional context in comments.

2 Comments

I think you have to fill your array: repl.it/@baruchiro/SphericalGrizzledTask
Thanks for bringing that up, I was not aware that those empty slots are not the same as undefined values.
11

Actually to create a fully optimized true c like fixed array in js on most modern browsers (including IE 11) you could use: TypedArray or ArrayBuffer like so:

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

1 Comment

This might be helpful to people if they need an array where entries all have the same data type. The original question already mentioned Float32Array as an example of this. I don't think it helps when the entries of the array might not be the same type.
9

You can simply use like this.

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

Basically it will fill items in array until length goes to 5 if length will grater the 5. It pop-out las item array. So it will maintain the length always 5.

Comments

7

Update:

The accepted answer shows how this issue can now be solved using Object.seal which wasn't available at the time.

Original Answer:

So, it seems that the answer to the original question is simply 'No'. It is not possible to create a native javascript Array with a fixed length.

But, you can create an object which will behave like a fixed-length Array. Following the suggestions in the comments, I've come up with 2 possible implementations, both with pros and cons.

I haven't figured out which of the 2 I'm going to use in my project yet. I'm not 100% satisfied with either. Please let me know if you have any ideas for improving them (I am keen to make these objects as fast and efficient as possible because I'm going to need lots of them).

Code for both implementations below, together with QUnit tests illustrating usage.

// 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');     

    });


});

1 Comment

I hope the conclusion was clear to you : don't do that.
6
  1. Use new Array constructor

However, the array created is filled with undefined. Thus make it non-iterable. You can fill it with null or 0 values instead.

new Array(100).fill(null).map(() => ...);
  1. Use Array.from method
Array.from({ length: n }, (_,i) => i) 

Comments

2

You can create an Array with fixed size of empty values with new Array(5). And keep their size fixed.

const fixedQueue = new Array(5)
fixedQueue.push = function (i) {
    this.shift();
    return Array.prototype.push.call(this, i)
}
fixedQueue.unshift = function (i) {
    this.pop()
    return Array.prototype.unshift.call(this, i)
}

Comments

0

I've written a array-fixed https://github.com/MatrixAI/js-array-fixed which is a library providing you with fixed length arrays and fixed length dense arrays (arrays which always has its elements collapsed left or collapsed right).

It supports many standard array operations such as splice and slice. But more operations can be added in the future.

The concept of push doesn't make sense, instead there is caret* methods available that insert an element and push out elements that already exist into empty slots.

Comments

0

We can use closure for this type of problem. We are just fixed the array size and return a function from a function.

    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);

Comments

0

You can implement a class with a capacity. Lets say you want the length to remain at 5 when while pushing into the array. If you run the code snippet, you will see that 6 did not push into the array because the capacity is already met. Best Regards.

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)

Comments

0

Array.pop already fails if the array is empty. You want a push to fail if it would breach the fixed size - so don't use Array.push just use a function instead:

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

I use a different type of fixed length array to save something like recent files. In this case you can keep pushing and the array will store just the last fixed number of items. Remember Array.push adds to the end of the array so to push another item you use splice(0,1) to remove the first item of the array.

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

Comments

0

let a = new Array(42)

by default, values will be undefined. e.g. a[0] to a[41] values will be undefined

Comments

-1

The current answer is YES, you can. There are severals ways to do that, but some web browsers has it's own "interpretation".

  1. Solution tested with 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 ]   

Is important to notice that if the array are object, you need to do, deep freeze instead. The code of deepfreeze is here.

  1. A Class that wraps an Array (it's better if you don't want to throw an exception)

  2. With ES2015 code should work the follow solution but it doesn't:

var x = new Array(10).fill(0);
Object.freeze( x.length );
x.push(3);
console.log(x);
Check this page in the section Note

7 Comments

Problem with 1 - you can't write the values. 3 looks good - maybe it will work one day.
Your third option is wrong. x.length is a number and Object.freeze(x.length); will do nothing except return x.length.
@tim-we I know and I wrote that, please read the answer and the comment. I said it should work some day but it doesn't. That's because freeze is supposed to be a watcher if the value change emits an error. It's the simplest solution for the question but it doesn't. And should work someday I open an issue and was accepted. So, someday it will work.
Source? The MDN page you linked reads "In ES2015, a non-object argument will be treated as if it were a frozen ordinary object, and be simply returned". Primitive values are being copied if you pass them as arguments. This would mean a fundamental change in how JS works. E.g. what would this do: 'var n = x.length; /*copy*/ Object.freeze(n);'
@time-wes is supposed to be a getter not a simple and direct access to the variable. That's why.
|
-1

I know this is an old question but nowadays there's a node module that does just this called fixed-array

1 Comment

That node module doesn't really help. If gives you an object representing a fixed array, but you can't access the values by index (you have to get a copy of the values as an array, but the copy is not fixed-length), and you can't write a value at a specific point either, only push to the end. An object wrapping an array as suggested in earlier answers is much more flexible.
-1

Yes, this function to create a fixed-length array so that it is dynamic and not just a single array, you can have different finite arrays just create a new variable and set its value to the return from createLimtedArray(array_limit), note my solution use push method so no extra method the array created in core with this no need to call any extra function when push. and good if going have more than one different length enum array

// this call back check the length and return true or false if length > limit
const theCallback = (theArray, limit)=>{
  const check = theArray.length <= limit;
  return theArray.length;
}

const createLimtedArray = (arrayLength)=>{
  const limtedArray = [];
  addPushEventListener(limtedArray, theCallback, arrayLength);
  return limtedArray;
}

function addPushEventListener(theArray, theCallback, limit){
  // change the push method
  theArray.push = (e)=> { 
  // call the normal push method and give it the item
  // apply something callback on array before push
  //if (theArray.length <= limit){return false;}
  Array.prototype.push.call(theArray, e);
  const arrLength = theCallback(theArray, e, limit);
  const acceptPush = (arrLength <= limit);
  const s = limit == 1 ? '' : 's';
  if (!acceptPush){
    console.log("sorry array only accept " + limit + " item" + s);
    theArray.pop();
  }
  // apply push or 
  };
}
// first limited array
let x = createLimtedArray(1);
console.log("---------------Array X----------------");
x.push("New X Item 1");
x.push("New X Item 2");
console.log(x);
console.log("");
let y = createLimtedArray(3);
y.push("New Y Item 1");
y.push("New Y Item 2");
y.push("New Y Item 3");
y.push("New Y Item 4");
console.log(y);
console.log("--------------------------------------");
console.log("");

another solution:

let y = [];
y[49] = 'value';
console.log(y.length);

or

[,,,,,,,,,,,,].length

if you want control value for all items use this method then

function fixedLengthArr(maxlength=0, defaultVal=null){
  const arr = [];
  for (let i=0; i<maxlength; i++){
      arr.push(defaultVal);
  }
  return arr;
}

document.querySelector("#demo").innerText = fixedLengthArr(10, '*');
<div id="demo"></div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.