An option, which does not modify the array properties, would be to use a Proxy
:
// funções que quero alterar
const functions = ['push', 'pop'];
// handler que intercepta uma chamada de função/método
var methodHandler = {
apply: function (target, thisArg, argumentsList) {
// aqui seria o "callback", a parte que eu faço antes de chamar a função/método original
console.log(`Chamando ${target.name} no array [${thisArg.join(',')}], com argumentos: ${argumentsList.join(',')}`);
// chamar a função/método original
return target.apply(thisArg, argumentsList);
}
};
// handler que intercepta propriedades do array
var arrayChangeHandler = {
get: function (target, prop) {
// se é uma das funções que você quer interceptar, retorne um proxy da função em vez dela mesma
if (typeof target[prop] === 'function' && functions.includes(target[prop].name)) {
return new Proxy(target[prop], methodHandler);
}
// se não for uma das funções a serem alteradas, ou se for qualquer outra propriedade, retorne-a sem modificação
return target[prop];
}
};
const arr = [];
const proxiedArray = new Proxy(arr, arrayChangeHandler);
proxiedArray.push('a', 'b', 'c');
proxiedArray.pop();
console.log(proxiedArray); // ['a', 'b']
// o array original também é modificado
console.log(arr); // ['a', 'b']
Here we do two main things:
- in the array, we define the Handler
get
, which is called when accessing a property of the object in question (in this case, the object is the array, and the property can be any one, including methods). However, this Handler does not intercept the method call, so we need the next item:
- in case the accessed property is one of the methods we want to change, I return another
Proxy
(but this is done in the method, not in the array, and I use the Handler apply
to intercept the call from it). This, in turn, executes something (in the example I just put console.log
, but it could be anything you wanted), and then calls the original method.
- if it is not one of the methods to be changed, simply return the property without modification
In this case, the output of the above code is:
Chamando push no array [], com argumentos: a,b,c
Chamando pop no array [a,b,c], com argumentos:
[ 'a', 'b' ]
[ 'a', 'b' ]
Note that even if calls are made on proxy, the original array is changed by them.
Of course there you can generalize, creating for example a more general function that receives an object and the callbacks to be executed when the methods are called. Something like:
// Recebe um objeto e os callbacks a serem executados quando os métodos são chamados.
// Cada função de callback recebe como parâmetros o objeto e os argumentos passados ao método original.
function proxyWithChangedMethods(object, callbacks) {
// handler que intercepta a chamda de uma função/método
var methodHandler = {
apply: function (target, thisArg, argumentsList) {
// chamar o respectivo callback
if (typeof callbacks[target.name] === 'function') {
callbacks[target.name](thisArg, argumentsList);
}
// chamar a função/método original
return Reflect.apply(target, thisArg, argumentsList);
}
};
const funcNames = Object.keys(callbacks);
// handler que intercepta propriedades do objeto
var objChangeHandler = {
get: function (target, prop) {
// se é uma das funções que você quer interceptar, retorne um proxy da função em vez dela mesma
if (typeof target[prop] === 'function' && funcNames.includes(target[prop].name)) {
return new Proxy(target[prop], methodHandler);
}
// se não for uma das funções a serem alteradas, ou se for qualquer outra propriedade, retorne-a sem modificação
return Reflect.get(target, prop);
}
};
return new Proxy(object, objChangeHandler);
}
// métodos que quero alterar, com os respectivos callbacks a serem chamados antes do método original
const callbacks = {
'push': function(array, args) {
console.log(`Adicionando os elementos ${args.join(', ')} no array [${array.join(',')}]`);
},
'pop': function(array, args) {
console.log(`Removendo o último elemento do array [${array.join(', ')}]`);
}
};
const arr = [];
const proxiedArray = proxyWithChangedMethods(arr, callbacks);
proxiedArray.push('a', 'b', 'c');
proxiedArray.pop();
console.log(proxiedArray);
// o array original também é modificado, ele só não executa os callbacks
console.log(arr); // [ 'a', 'b' ]
arr.push('d'); // não executa o callback
console.log(arr) // // [ 'a', 'b', 'd' ]
console.log(proxiedArray) // // [ 'a', 'b', 'd' ]
I also exchanged the method call and the acquisition of property for the object Reflect
. There are some differences between Reflect.apply
and Function.prototype.apply
(one of them is that if the prototype of Function
have a apply
superscript, Reflect.apply
will not be affected - see link for more details).
The exit code above is:
Adicionando os elementos a, b, c no array []
Removendo o último elemento do array [a, b, c]
[ 'a', 'b' ]
[ 'a', 'b' ]
[ 'a', 'b', 'd' ]
[ 'a', 'b', 'd' ]
Of course, if you want to modify the methods of the array itself, then just do as the another answer. And just to leave another alternative (very similar to your):
// métodos que quero alterar, com os respectivos callbacks a serem chamados antes do método original
const callbacks = {
'push': function (array, args) {
console.log(`Adicionando os elementos ${args.join(', ')} no array [${array.join(',')}]`);
},
'pop': function (array, args) {
console.log(`Removendo o último elemento do array [${array.join(', ')}]`);
}
};
const arr = [];
for (const[funcName, callback] of Object.entries(callbacks)) {
const prop = arr[funcName];
if (typeof prop === 'function') { // só pra garantir :-)
const originalFunc = prop;
Object.defineProperty(arr, funcName, { configurable: true, enumerable: false, writable: true,
value: function (...args) {
callback(arr, args); // chama o callback, passa o array e os argumentos do método
return Reflect.apply(originalFunc, arr, args); // chama o método original
}
});
}
}
arr.push('a', 'b', 'c');
arr.pop();
console.log(arr); // ['a', 'b']
The exit is:
Adicionando os elementos a, b, c no array []
Removendo o último elemento do array [a, b, c]
[ 'a', 'b' ]
Only one detail: instead of
arr.push(...[1, 2, 3])
, why not just doarr.push(1, 2, 3)
? After all, with fixed values there is no reason to create the array and then use spread in it... and in the last code, I thinkarr2
was left over. Finally, don’t forget thereof :-)– hkotsubo