Implémenter un comportement similaire à un tableau de bord en JavaScript sans utiliser Array

Existe-t-il un moyen de créer un objet similaire à un tableau dans JavaScript, sans utiliser le tableau intégré? Je suis particulièrement préoccupé par un comportement comme celui-ci:

var sup = new Array(5); //sup.length here is 0 sup[0] = 'z3ero'; //sup.length here is 1 sup[1] = 'o3ne'; //sup.length here is 2 sup[4] = 'f3our'; //sup.length here is 5 

Le comportement particulier que je vois ici est que sup.length change sans que des méthodes soient appelées. Je comprends de cette question que l'opérateur [] est surchargé dans le cas des tableaux, ce qui explique ce comportement. Existe-t-il une manière pure-javascript de dupliquer ce comportement, ou la langue n'est-elle pas suffisamment flexible pour cela?

Selon les docs de Mozilla , les valeurs retournées par regex font également des choses funky avec cet index. Est-ce possible avec un javascript simple?

Maintenant, nous avons ECMAScript 2015 (ECMA-262 6ème édition, ES6), nous avons des objets proxy , et ils nous permettent de mettre en œuvre le comportement du Array dans la langue elle-même, quelque chose comme suit:

 function FakeArray() { const target = {}; Object.defineProperties(target, { "length": { value: 0, writable: true }, [Symbol.iterator]: { // http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype-@@iterator value: () => { let index = 0; return { next: () => ({ done: index === target.length, value: target[index++] }) }; } } }); const isArrayIndex = function(p) { /* an array index is a property such that ToString(ToUint32(p)) === p and ToUint(p) !== 2^32 - 1 */ const uint = p >>> 0; const s = uint + ""; return p === s && uint !== 0xffffffff; }; const p = new Proxy(target, { set: function(target, property, value, receiver) { // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-exotic-objects-defineownproperty-p-desc if (property === "length") { // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-arraysetlength const newLen = value >>> 0; const numberLen = +value; if (newLen !== numberLen) { throw RangeError(); } const oldLen = target.length; if (newLen >= oldLen) { target.length = newLen; return true; } else { // this case gets more complex, so it's left as an exercise to the reader return false; // should be changed when implemented! } } else if (isArrayIndex(property)) { const oldLenDesc = Object.getOwnPropertyDescriptor(target, "length"); const oldLen = oldLenDesc.value; const index = property >>> 0; if (index > oldLen && oldLenDesc.writable === false) { return false; } target[property] = value; if (index > oldLen) { target.length = index + 1; } return true; } else { target.property = value; return true; } } }); return p; } 

Je ne peux pas garantir que cela est vraiment totalement correct, et il ne gère pas le cas où vous modifiez la longueur pour être plus petit que sa valeur précédente (le comportement est un peu complexe pour obtenir correctement, à peu près il supprime les propriétés de sorte que la length La propriété invariante détient), mais il donne une description approximative de la façon dont vous pouvez l'implémenter. Il n'imite pas non plus le comportement de [[Call]] et [[Construct]] sur Array , ce qui est autre chose que vous ne pouviez pas faire avant ES6: il n'était pas possible d'avoir un comportement divergent entre les deux dans le code ES , Même si rien de tout cela n'est dur.

Cela implémente la propriété length de la même manière que la spécification la définit comme fonction: elle intercepte les affectations aux propriétés de l'objet et modifie la propriété length s'il s'agit d'un "array index".

Contrairement à ce que l'on peut faire avec ES5 et getters, cela permet d'obtenir une length en temps constant (évidemment, cela dépend toujours de l'accès de propriété sous-jacent dans VM en temps constant) et le seul cas dans lequel il fournit un temps non constant La performance est le cas non implémenté lorsque les propriétés newLen - oldLen sont supprimées (et la suppression est lente dans la plupart des machines virtuelles!).

L'opérateur [] est la manière native d'accéder aux propriétés de l'objet. Il n'est pas disponible dans la langue à ignorer afin de modifier son comportement.

Si ce que vous voulez, ce sont les valeurs calculées de retour sur l'opérateur [], vous ne pouvez pas le faire en JavaScript car la langue ne supporte pas le concept de propriété calculée. La seule solution consiste à utiliser une méthode qui fonctionnera de la même manière que l'opérateur [].

 MyClass.prototype.getItem = function(index) { return { name: 'Item' + index, value: 2 * index }; } 

Si ce que vous voulez, avez le même comportement qu'un tableau natif dans votre classe, il est toujours possible d'utiliser des méthodes de matrice native directement sur votre classe. En interne, votre classe stockera des données comme un réseau natif, mais gardera son état de classe. JQuery fait cela pour que la classe jQuery ait un comportement de tableau tout en conservant ses méthodes.

 MyClass.prototype.addItem = function(item) { // Will add "item" in "this" as if it was a native array // it will then be accessible using the [] operator Array.prototype.push.call(this, item); } 

Oui, vous pouvez sous-classer un tableau dans un objet arraylike facilement en JavaScript:

 var ArrayLike = function() {}; ArrayLike.prototype = []; ArrayLike.prototype.shuffle = // ... and so on ... 

Vous pouvez alors instancier de nouveaux objets similaires à un tableau:

 var cards = new Arraylike; cards.push('ace of spades', 'two of spades', 'three of spades', ... cards.shuffle(); 

Malheureusement, cela ne fonctionne pas dans MSIE. Il ne tient pas compte de la propriété length . Ce qui défléchit tout le tout.

Le problème plus en détail sur Dean Edwards ' Comment sous-classer l'objet Array JavaScript . Il s'est avéré que sa solution de contournement n'était pas sûre car certains bloqueurs de popup l'empêcheraient.

Mise à jour: Il vaut la peine de mentionner Juriy "kangax" Zobtsev absolument épique publication sur le sujet. Cela couvre presque tous les aspects de ce problème.

C'est ce que vous cherchez?

 Thing = function() {}; Thing.prototype.__defineGetter__('length', function() { var count = 0; for(property in this) count++; return count - 1; // don't count 'length' itself! }); instance = new Thing; console.log(instance.length); // => 0 instance[0] = {}; console.log(instance.length); // => 1 instance[1] = {}; instance[2] = {}; console.log(instance.length); // => 3 instance[5] = {}; instance.property = {}; instance.property.property = {}; // this shouldn't count console.log(instance.length); // => 5 

Le seul inconvénient est que la «longueur» sera répercutée dans les boucles for..in comme s'il s'agissait d'une propriété. Dommage qu'il n'y ait pas un moyen de définir des attributs de propriété (c'est une chose que je souhaite vraiment pouvoir faire).

Surtout, vous n'avez pas besoin d'une taille d'index prédéfinie pour les tableaux en javascript, vous pouvez simplement:

 var sup = []; //Shorthand for an empty array //sup.length is 0 sup.push(1); //Adds an item to the array (You don't need to keep track of index-sizes) //sup.length is 1 sup.push(2); //sup.length is 2 sup.push(4); //sup.length is 3 //sup is [1, 2, 4] 

Si vous êtes préoccupé par la performance avec votre réseau dispersé (bien que vous ne devriez probablement pas l'être) et que vous vouliez vous assurer que la structure était aussi longue que les éléments que vous lui avez remis, vous pouvez le faire:

 var sup = []; sup['0'] = 'z3ero'; sup['1'] = 'o3ne'; sup['4'] = 'f3our'; //sup now contains 3 entries 

Encore une fois, il convient de noter que vous ne verrez probablement aucun gain de performance en faisant cela. Je soupçonne que Javascript gère déjà très peu les tableaux clairs, merci beaucoup.

La réponse est: il n'y a aucun moyen à partir de maintenant. Le comportement du tableau est défini dans ECMA-262 comme un comportement de cette manière, et comporte des algorithmes explicites sur la façon de traiter l'établissement et le paramétrage des propriétés du tableau (et non des propriétés génériques des objets). Cela me trouble un peu = (.

Vous pouvez également créer votre propre méthode de longueur comme:

 Array.prototype.mylength = function() { var result = 0; for (var i = 0; i < this.length; i++) { if (this[i] !== undefined) { result++; } } return result; } 

Bien sûr, vous pouvez reproduire presque n'importe quelle structure de données en JavaScript, tous les éléments de base sont là. Ce que vous allez finir sera plus lent et moins intuitif.

Mais pourquoi ne pas utiliser push / pop?