Reputation: 87
I would like to associate some side effect with every array accessor like a[i]
. For example, if the side effect is writing a message to the console, the following program:
var array = [1, 2, 3]
var total = 0;
for (var i in array) {
total += array[i]
}
console.log(total);
1 // access a[0]
2 // access a[1]
3 // access a[2]
6 // print original total
In case I was interested in intercepting an array method push
, I'd use the technique from this blog post and provided an interceptor:
var _push = Array.prototype.push;
Array.prototype.push = function( item ) {
console.log( 'pushing ', item, ' into ', this );
_push.apply( this, arguments );
}
Is it possible apply the same trick to an array accessor? Or what would be a better solution for this problem? One important note is that I don't want to modify the original code of the program. Therefore, using JS proxies
to intercept getters and setters doesn't seem te be a valid option for my problem.
One particular side effect, I want to introduce is raising an exception in case the accessed value is undefined (some variation of the index out of bounds exception for JS arrays.) I would check if the currently accessing value is equal to undefined
, and throw an exception, in that case, otherwise just return the original value.
Upvotes: 5
Views: 3024
Reputation: 20228
It is not possible without modifying the code:
You can't override the array literal constructor. See Overwriting the Array constructor does not affect [], right?
You also can't override the behavior of the iterator used by the for ... in loop. See https://tc39.github.io/ecma262/#sec-enumerate-object-properties
While you can add properties with intercepting getter functions on Array.prototype
, those prototype properties won't be accessed when properties of the same name exist on the array instance as is the case in your example.
So you need to either modify the code, preprocess your code or modify the JavaScript engine running the code.
In the first two cases, I recommend replacing array literals with explicit calls to the Array
constructor, which can be overriden:
// Override default array constructor:
Array = (function(Array) {
function LoggingArray(...args) {
return new Proxy(Array(...args), {
get: function(target, property) {
console.log(target[property]);
return Reflect.get(target, property);
}
});
}
Object.setPrototypeOf(LoggingArray, Array);
LoggingArray.prototype = Array.prototype;
return LoggingArray;
})(Array);
// Original code without array literal:
var array = Array(1, 2, 3);
var total = 0;
for (var i in array) {
total += array[i]
}
console.log(total);
Upvotes: 2
Reputation: 9988
You can't override that accessor of Arrays. Here an example:
Object.defineProperty(Array.prototype, 0, {
get: function () { return "my get on 0"; }
});
var a = [1,2,3];
console.log(a[0]); // output: 1
But if you try to do the same with a property which doesn't really exist in the array, you will achieve it:
Object.defineProperty(Array.prototype, 5, {
get: function () { return "my get on 5"; }
});
var a = [1,2,3];
console.log(a[5]); // output: my get on 5
What you can do is a little workaround accessing the elements through the get
method of Arrays.
Array.prototype.get = function(i) {
console.log('my print');
console.log(this[i]);
return "this is!";
};
var a = [1,2,3];
console.log(a.get(0)); // output: my print 1 this is!
So, coming back to your question you could do something like you did for push
but with the get
, avoiding proxies:
Array.prototype.get = function (i) {
console.log('Accessing element: ' + this[i]);
console.log(this);
return this[i];
};
var array = [1, 2, 3];
var total = 0;
// be careful that now you cannot do anymore
// for (var i in array), because inside the array there is also the property get defined and it will cycle also on that
// if you want to cycle again in that way, you need the check through hasOwnProperty method
/*
for(var i in array) {
if (array.hasOwnProperty(i)){
console.log(i);
total += array.get(i);
}
}
*/
for(var i = 0; i < array.length; i++) {
total += array.get(i);
}
console.log(total);
Just for completing the answer, what you are trying to do can be done in one line with reduce
method of Arrays:
var array = [1, 2, 3];
var result = array.reduce(function (accumulator, actual) {
return accumulator + actual;
}, 0);
console.log(result);
I strongly recommend you to avoid the override of these accessors. You will change the basis of the code so it will be impossible for third party people to understand what's going on without reading all the code. Moreover you will lose a lot of built-in useful methods. I hope this helps
p.s. following to your edit, for checking undefined values and raise exceptions you can add the check inside the override of the get method.
But my suggestion is just to filter the array, detect the undefined values and get rid of them.
Note that I am using the double equal. because undefined == null
but undefined !== null
.
In this way you will remove both undefined and null values. If you want to remove only undefined, change it to if (typeof element === 'undefined')
.
So something like this, using just one loop with arrays filter
method:
var data = [1, 2, undefined, 3, 4, undefined, 5];
data = data.filter(function( element, index ) {
// note that I am using the double equal. because undefined == null but undefined !== null.
// in this way you will remove both undefined and null values
// if you want to remove only undefined, change it to if (typeof element === 'undefined')
if (element == null) {
console.log('found and undefined null value at index: ' + index);
}
return element != null;
});
console.log(data); // array without undefined and null values
Upvotes: 4