web-dev-qa-db-fra.com

Est-il possible de créer un tableau de longueur fixe en javascript?

Est-il possible, en Javascript, de créer un tableau dont la longueur reste garantie?

Par exemple, le tableau A est créé avec la longueur 2. Par la suite, toute tentative d'appel de A.Push () ou A.pop () ou de définition de la valeur de A [5] échouera. La longueur sera toujours 2.

C’est ainsi que les tableaux typés (par exemple, Float32Array) fonctionnent déjà. Ils ont une taille fixe. Mais je veux un moyen d'avoir le même comportement sur un Array standard.

Pour ma situation spécifique, je voudrais créer un tableau de longueur fixe où chaque entrée est un objet. Mais j'aimerais quand même connaître la réponse à la question générale.

15
Daniel Howard

Mettre à jour:

La réponse acceptée montre comment ce problème peut est maintenant résolu à l'aide de Object.seal qui n'était pas disponible à ce moment-là.

Réponse originale:

Il semble donc que la réponse à la question initiale soit simplement "Non". Il n'est pas possible de créer un tableau javascript natif avec une longueur fixe.

Mais, vous pouvez créer un objet qui se comportera comme un tableau de longueur fixe. À la suite des suggestions formulées dans les commentaires, j'ai proposé deux implémentations possibles, à la fois pour et contre.

Je ne sais pas encore lequel des 2 je vais utiliser dans mon projet pour le moment. Je ne suis pas à 100% satisfait non plus. Faites-moi savoir si vous avez des idées pour les améliorer (je tiens à rendre ces objets aussi rapides et efficaces que possible, car j'en aurai besoin de beaucoup).

Code pour les deux implémentations ci-dessous, ainsi que des tests QUnit illustrant l'utilisation.

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

    });


});
5
Daniel Howard

La réponse actuelle est OUI, vous le pouvez. Il y a plusieurs façons de le faire, mais certains navigateurs Web ont leur propre "interprétation".

  1. Solution testée avec 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 ]   

Il est important de noter que si le tableau est un objet, vous devez le faire, mais le congeler à la place. Le code de congélation est ici .

  1. Une classe qui enveloppe un tableau (c'est mieux si vous ne voulez pas lancer d'exception)

  2. Avec le code ES2015, la solution suivante devrait fonctionner mais elle ne fonctionne pas:

var x = new Array(10).fill(0);
Object.freeze( x.length );
x.Push(3);
console.log(x);
Consultez cette page dans la section Note

1
titusfx

En fait, pour créer un vrai c parfaitement optimisé comme un tableau fixe en js sur la plupart des navigateurs modernes (y compris IE 11), vous pouvez utiliser: TypedArray ou ArrayBuffer comme ceci:

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/ArrayBufferhttps://developer.mozilla.org/en-US/docs/ Web/JavaScript/Référence/Global_Objects/TypedArray

1
Micheal Kris

J'ai écrit un tableau corrigé https://github.com/MatrixAI/js-array-fixed , une bibliothèque qui fournit des tableaux de longueur fixe et des tableaux denses de longueur fixe gauche ou effondré à droite).

Il prend en charge de nombreuses opérations de matrice standard telles que l'épissure et la tranche. Mais d'autres opérations peuvent être ajoutées dans le futur.

Le concept de Push n'a pas de sens. Il existe plutôt des méthodes caret* qui insèrent un élément et extraient des éléments déjà existants dans des emplacements vides.

0
CMCDragonkai