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;
});
}
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.
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.
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.
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:
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.
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(', ') + ']';
}
}
);
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>
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]
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;
}
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.
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 .