5

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);
should return the output like:

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.

4
  • 2
    Possible duplicate of Detecting changes in a Javascript array using the Proxy object Commented May 21, 2017 at 13:15
  • @Mohamed that linked question doesn't help OP with overriding the general behaviour of built-in arrays though. Commented May 21, 2017 at 13:19
  • I agree with @le_m Commented May 21, 2017 at 13:33
  • @le_m but you can use it to log / throw an exception wich was asked... Commented May 21, 2017 at 15:54

2 Answers 2

4

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

Sign up to request clarification or add additional context in comments.

8 Comments

thanks for the reply. if what I want to achieve is not feasible, I'll have to fall back on the option with the get method suggested by you. I also updated the question a bit, clarifying the kind of exception I care most.
you can change the get method in order to check if it is undefined before and raise the exception, but my suggestion is another one in this case. I will add it to the answer, it is easier to write code than in comments
changed it adding at the end your specific case
I didn't know the difference between == and === for undefined. It was useful! I don't actually want to remove anything from the array, just track if it's accessed out of bounds. By default, JS doesn't give any exception in that scenario, but I want to know when it happens.
Maybe I got what it is happening to you :) you are working on an app wrote by someone else, quite big, they defined a lot of arrays but they didnt manage undefined cases, so you are having a lot of errors everywhere. So now you want a way to change directly the prototype so you dont have to refactor all the code. Isn'it? :D
|
2

It is not possible without modifying the code:

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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.