I haven't found any information on this topic so forgive me please if this is a very weird question.
I know JS allows to define properties as accessors meaning they fire a getter or a setter function when used.
My question is whether the same can be done to array members.
For example, I want a setter function to fire when assigning like this:
myObj[2] = 2 /* set function(value, index) { console.log(value + index) } */
If this is not possible, are there any other ways one can extend the [] operation?
I haven't found any information on this topic so forgive me please if this is a very weird question.
I know JS allows to define properties as accessors meaning they fire a getter or a setter function when used.
My question is whether the same can be done to array members.
For example, I want a setter function to fire when assigning like this:
myObj[2] = 2 /* set function(value, index) { console.log(value + index) } */
If this is not possible, are there any other ways one can extend the [] operation?
Share Improve this question asked Oct 13, 2016 at 19:45 user1533299user1533299 1031 silver badge6 bronze badges 3-
1
Do you mean for the property "
2
" only or for all indices? – Bergi Commented Oct 13, 2016 at 20:14 - @Bergi all indices – user1533299 Commented Oct 14, 2016 at 13:01
- Then you want a proxy, yes. See also here – Bergi Commented Oct 14, 2016 at 13:12
4 Answers
Reset to default 6Basically unless you subclass an array you can not.
Even if you subclass, arrays are much more dynamic than objects. Unlike objects they get items added and removed all the time and you have to be able to dynamically attach getters and setters to each property (key) along with the added item. Not practical. If an array is sub-classed you may modify it's length property with getters and setters and try to watch the array from the changes to it's length property but this requires a lot of array diffing.
I guess with the arrays it's best to use ES6 Proxy object. You might do it as follows;
function ProxyMaker(a){
return new Proxy(a, {
get: function(target, property, receiver) {
console.log(target+"'s "+property+" property has been accessed");
return target[property];
},
set: function(target, property, value, receiver) {
console.log(target+"'s "+property+" property has been modified");
return target[property] = value;
},
has: function(target, property) {
console.log(target+"'s "+property+" property has been checked");
return Reflect.has(target, property); // 10x to @Bergi
}
});
}
var p = ProxyMaker([]);
p[0] = 1;
p[0];
p.push(10);
p.unshift(7);
console.log(JSON.stringify(p))
If it is critical to know when a value has changed you would need to create explicit setters and getters.
As suggested Proxy is a good option if available. It may not be available to in all browsers though. In this case you could create a new object that notifies you when it was set.
There a lot of trade offs with this approach, and it would be a fair amount of work to implement everything array natively provides. The example below is pretty bare-bones but it does provide array like interactions and a setter event to listen to.
var observableArray = function(eventName) {
var myArray = [];
var api = {};
api.get = function(index) {
if (myArray.length && typeof index === 'number') {
return myArray[index];
}
};
api.set = function(item, index) {
if (typeof index === 'number') {
myArray[index] = item;
}
else {
index = myArray.push(item) - 1;
}
var event = new CustomEvent(eventName, {detail: index});
document.dispatchEvent(event);
return index;
};
api.getLength = function() {
return myArray.length;
};
api.empty = function() {
myArray.splice(0, myArray.length);
};
return api;
};
var arrayListener = document.addEventListener('foo', function(e) {console.log('The index modified: ',e.detail);},false);
var test = new observableArray('foo');
test.set('bar');
console.log('The # of items: ', test.getLength());
test.empty();
console.log('The # of items after empty: ', test.getLength());
If you only needed a way to be able to make something happen when an array value is set, you could extend the array to have a set method that fires a callback. It would be up to the programmer to use it though.
Array.prototype.set = function(item, index, callback) {
if (typeof index === 'number') {
this[index] = item;
}
else {
index = this.push(item) - 1;
}
if (callback) {
callback.apply(this, [index]);
}
return index;
};
var test2 = [];
test2.set('foo', 4, function(i) {
console.log('Callback value: ', i);
console.log('The array: ', this);
});
What you can do is wrapping the object in an ES6 proxy.
var a = new Proxy({}, {get:function(target, name){return 42;}});
console.log(a[0]); // ==> 42
then you can trap and add processing to get
/set
operations.
You have two ways of accessing arrays:
treated like an object with a named setter/getter var x = pojoArray['firstName'];
through the numerical index var x = pojoArray[10];
So you have two ways of approaching this as I see it:
Bad Way: If you treat the array like an object, you could loop the entire array manually but that should be avoided while using arrays like objects because order is not guaranteed. See the following post: Loop through an array in JavaScript
Other Option: Per your requirement, the other way to do this is to access it through the numerical index. You could create some sort of helper functions that will have to map a getter and setter key with the proper index in the array and ensure that that key is only used once.
Notice that I did not state a 'good way' to fulfill your requirement. I would try and avoid either of these workflows if at all possible, as they will be prone to errors and cause headache down the road.