web-dev-qa-db-fra.com

Getter/setter sur le tableau javascript?

Existe-t-il un moyen d'obtenir un comportement get/set sur un tableau? J'imagine quelque chose comme ça:

var arr = ['one', 'two', 'three'];
var _arr = new Array();

for (var i = 0; i < arr.length; i++) {
    arr[i].__defineGetter__('value',
        function (index) {
            //Do something
            return _arr[index];
        });
    arr[i].__defineSetter__('value',
        function (index, val) {
            //Do something
            _arr[index] = val;
        });
}
29
Martin Hansen

L'accès aux tableaux n'est pas différent de l'accès aux propriétés normales. array[0] signifie array['0'], vous pouvez donc définir une propriété avec le nom '0' et intercepter l'accès au premier élément du tableau par ce biais.

Cependant, cela ne le rend pas pratique pour tous, sauf les tableaux de longueur plus ou moins fixe. Vous ne pouvez pas définir une propriété pour «tous les noms qui se trouvent être des entiers» en une seule fois.

16
bobince

En utilisant Proxies , vous pouvez obtenir le comportement souhaité:

var _arr = ['one', 'two', 'three'];

var accessCount = 0;
function doSomething() {
  accessCount++;
}

var arr = new Proxy(_arr, {
  get: function(target, name) {
    doSomething();
    return target[name];
  }
});

function print(value) {
  document.querySelector('pre').textContent += value + '\n';
}

print(accessCount);      // 0
print(arr[0]);           // 'one'
print(arr[1]);           // 'two'
print(accessCount);      // 2
print(arr.length);       // 3
print(accessCount);      // 3
print(arr.constructor);  // 'function Array() { [native code] }'
<pre></pre>

Le constructeur Proxy créera un objet enveloppant notre tableau et utilisera des fonctions appelées interruptions pour remplacer les comportements de base. La fonction get sera appelée pour any lookup property, et doSomething() avant de renvoyer la valeur.

Les proxies sont une fonctionnalité ES6 et ne sont pas pris en charge dans IE11 ou une version antérieure. Voir liste de compatibilité du navigateur.

33
acbabis

J'ai cherché dans l'article de John Resig JavaScript Getters And Setters , mais son exemple de prototype n'a pas fonctionné pour moi. Après avoir essayé quelques alternatives, j'en ai trouvé une qui semblait fonctionner. Vous pouvez utiliser Array.prototype.__defineGetter__ de la manière suivante:

Array.prototype.__defineGetter__("sum", function sum(){
var r = 0, a = this, i = a.length - 1;
do {
    r += a[i];
    i -= 1;
} while (i >= 0);
return r;
});
var asdf = [1, 2, 3, 4];
asdf.sum; //returns 10

Travaillé pour moi dans Chrome et Firefox.

4
rolandog

Il est possible de définir des tableaux Getters and Setters for JavaScript. Mais vous ne pouvez pas avoir des accesseurs et des valeurs en même temps. Voir la Mozilla documentation :

Il n'est pas possible d'avoir simultanément un getter lié à une propriété et que cette propriété détienne réellement une valeur

Donc, si vous définissez des accesseurs pour un tableau, vous devez avoir un deuxième tableau pour la valeur réelle. Le exemple suivant l’illustre.

//
// Poor man's prepare for querySelector.
//
// Example:
//   var query = prepare ('#modeler table[data-id=?] tr[data-id=?]');
//   query[0] = entity;
//   query[1] = attribute;
//   var src = document.querySelector(query);
//
var prepare;
{
  let r = /^([^?]+)\?(.+)$/; // Regular expression to split the query

  prepare = function (query, base)
  {
    if (!base) base = document;
    var q  = []; // List of query fragments
    var qi = 0;  // Query fragment index
    var v  = []; // List of values
    var vi = 0;  // Value index
    var a  = []; // Array containing setters and getters
    var m;       // Regular expression match
    while (query) {
      m = r.exec (query);
      if (m && m[2]) {
        q[qi++] = m[1];
        query   = m[2];
        (function (qi, vi) {
          Object.defineProperty (a, vi, {
            get: function() { return v[vi]; },
            set: function(val) { v[vi] = val; q[qi] = JSON.stringify(val); }});
        })(qi++, vi++);
      } else {
        q[qi++] = query;
        query   = null;
      }
    }
    a.toString = function () { return q.join(''); }
    return a;
  }
}

Le code utilise trois tableaux:

  1. un pour les valeurs réelles,
  2. un pour les valeurs codées JSON
  3. et un pour les accesseurs.

Le tableau avec les accesseurs est renvoyé à l'appelant. Lorsqu'un set est appelé en attribuant une valeur à l'élément de tableau, les tableaux contenant les valeurs simples et codées sont mis à jour. Lorsque get est appelé, il ne renvoie que la valeur simple. Et toString renvoie la requête entière contenant les valeurs codées.

Mais comme d’autres l’ont déjà dit, cela n’a de sens que lorsque la taille du tableau est constante. Vous pouvez modifier les éléments existants du tableau mais vous ne pouvez pas ajouter d'éléments supplémentaires.

1
ceving

J'espère que ça aide.

Object.extend(Array.prototype, {
    _each: function(iterator) {
                    for (var i = 0; i < this.length; i++)
                    iterator(this[i]);
                },
    clear: function() {
                    this.length = 0;
                    return this;
                },
    first: function() {
                    return this[0];
                },
    last: function() {
                return this[this.length - 1];
                },
    compact: function() {
        return this.select(function(value) {
                                                return value != undefined || value != null;
                                                }
                                            );
        },
    flatten: function() {
            return this.inject([], function(array, value) {
                    return array.concat(value.constructor == Array ?
                        value.flatten() : [value]);
                    }
            );
        },
    without: function() {
        var values = $A(arguments);
                return this.select(function(value) {
                        return !values.include(value);
                }
            );
    },
    indexOf: function(object) {
        for (var i = 0; i < this.length; i++)
        if (this[i] == object) return i;
        return -1;
    },
    reverse: function(inline) {
            return (inline !== false ? this : this.toArray())._reverse();
        },
    shift: function() {
        var result = this[0];
        for (var i = 0; i < this.length - 1; i++)
        this[i] = this[i + 1];
        this.length--;
        return result;
    },
    inspect: function() {
            return '[' + this.map(Object.inspect).join(', ') + ']';
        }
    }
);
1
Martinez

Cette réponse est juste une extension de la solution basée sur le proxy. Voir la solution avec proxy, en ce sens que seulement get est mentionné mais nous pouvons aussi utiliser set comme je le montre ici.

Remarque: le 3ème argument de l'ensemble peut porter la valeur ...

Le code est explicite.

var _arr = ['one', 'two', 'three'];

var accessCount = 0;
function doSomething() {
accessCount++;
}

var arr = new Proxy(_arr, {
  get: function(target, name) {
  doSomething();
  return target[name];
},
set: function(target, name, val) { doSomething(); target[name] = val; }
});

function print(value) {
document.querySelector('pre').textContent += value + '\n';
}

print(accessCount);      // 0
print(arr[0]);           // 'one'
print(accessCount);      // 1
arr[1] = 10;
print(accessCount);      // 2
print(arr[1]);           // 10

<pre></pre>
0
Tibin Thomas

c'est comme ça que je fais les choses. Vous devrez modifier la création du prototype (j'ai enlevé un peu de ma version). Mais cela vous donnera le comportement par défaut du getter/setter auquel je suis habitué dans d'autres langages basés sur des classes. Définir un Getter et aucun Setter signifie que l'écriture sur l'élément sera ignorée ...

J'espère que cela t'aides.

function Game () {
  var that = this;
  this._levels = [[1,2,3],[2,3,4],[4,5,6]];

  var self = {
    levels: [],
    get levels () {
        return that._levels;
    },
    setLevels: function(what) {
        that._levels = what;
        // do stuff here with
        // that._levels
    }
  };
  Object.freeze(self.levels);
  return self;
}

Cela me donne le comportement attendu de:

var g = new Game()
g.levels
/// --> [[1,2,3],[2,3,4],[4,5,6]]
g.levels[0]
/// --> [1,2,3]

Reprenant la critique de dmvaldman: écrire devrait maintenant être impossible. J'ai réécrit le code en 1) n'utilisant pas d'éléments supprimés (__ defineGetter __) et 2) n'acceptant aucune écriture (c'est-à-dire: écriture non contrôlée) dans l'élément levels. Un exemple de setter est inclus. (J'ai dû ajouter un espacement à __ defineGetter à cause du démarquage)

À la demande de dmvaldmans:

g.levels[0] = [2,3,4];
g.levels;
/// --> [[1,2,3],[2,3,4],[4,5,6]]

//using setter
g.setLevels([g.levels, g.levels, 1,2,3,[9]]);
g.levels;
/// --> [[[1,2,3],[2,3,4],[4,5,6]],[[1,2,3],[2,3,4],[4,5,6]], ....]

//using setLevels
g.setLevels([2,3,4]);
g.levels;
/// --> [2,3,4]
0
LeTigre

Vous pouvez ajouter les méthodes de votre choix à une Array en les ajoutant à Array.prototype. Voici un exemple qui ajoute un getter et un setter

Array.prototype.get = function(index) {
  return this[index];
}

Array.prototype.set = function(index, value) {
  this[index] = value;
}
0
Dónal

Pourquoi ne pas créer une nouvelle classe pour les objets internes?

var a = new Car();

function Car()
{
   // here create the setters or getters necessary
}

Et alors,

arr = new Array[a, new Car()]

Je pense que vous avez l'idée.

0
jpabluz

Il est possible de créer des paramètres pour chaque élément d'un tableau, mais il y a une limitation: vous ne pourrez pas définir directement des éléments de tableau pour les index situés en dehors de la région initialisée (par exemple, myArray[2] = ... // wouldn't work if myArray.length < 2). (par exemple, Push, Pop, Splice, Shift, Unshift.) Je donne un exemple de la façon de réaliser ceci ici .

0
Constablebrew