Reputation: 446
I want to create an Class
, which is also a wrapper around a regular Array
, however I want some custom behaviour to happen when referencing the items on the instance of the class via their index.
To demo what I want to achieve:
class Custom {
constructor (arr) {
this.arr = arr;
}
method (str) {
this.arr.forEach(item => {
console.log(`${item} ${str}`);
})
}
[Magic.here] () {
// this part should invoke the constructor of this class with a single item of the array passed into it as an array of one as argument.
}
}
let c = new Custom(['something', 'other thing', 'hello?']);
c[1].method('exists?') // -> other thing exists?
Now, I am not entirely sure that it is possible. I did manage to come up with not-too-great solutions of my own, by extend
ing Array
. Proxy
also came into my mind, but couldn't get a working solution down.
Is it even possible and if so, what is the best way?
Upvotes: 5
Views: 1881
Reputation: 446
After reading these answers and digging some more I managed to come up with a full solution, using Proxy
. I am posting this here, in case someone on the internet comes up with a crazy idea like mine and wants an easy solution.
I annotated the code for a simple explanation:
/**
* The class I want to proxy on
*/
class Random {
constructor (arr) {this.arr=arr}
method () {this.arr.forEach(el=>{console.log(el)})}
}
// Creating a separate function for initializing the class. This will have to be used instead of the regular class' constructor if we want to use the proxy as well.
function init (arr) {
// Creating a new instance of random class based on arr, passing it into proxy
var p = new Proxy(new Random(arr), {
// Modifying the get function on handler
get: function (target, name) {
// Checking if the name is a numeric reference
if (typeof name === 'string' && /^-?\d+$/.test(name)) {
// ... it is, so we call init with the selected item in the array on our class object
let r = init([target.arr[name]]);
// finally we return the new proxy from init
return r;
}
else {
// otherwise we are looking at a direct reference, maybe to a method or scope variable, like random.method()
return target[name]
}
}
})
// we return the proxy
return p;
}
let random = init(['hello', 'amazing', 'world'])
console.log(random[0]); // proxy reference to array of hello
console.log(random[1]); // proxy reference to array of amazing
random.method(); // logs out all 3 items of array
random[2].method(); // logs out third item
Thanks to everyone who contributed.
Happy coding :)
Upvotes: 1
Reputation: 664326
Yes, you are looking for a proxy:
const isArrayIndex = str => (str >>> 0) + '' === str && str < 4294967295;
const arrayCustomizer = {
get(target, property, receiver) {
var el = Reflect.get(target, property, receiver);
if (isArrayIndex(property) && el != null)
el = new Custom(el);
return el;
}
}
class Custom {
constructor(v) {
this.value = v;
}
valueOf() {
return this.value;
}
method(arg) {
console.log(this.value + " " + arg.replace("?", "!"));
}
}
let c = new Proxy(['something', 'other thing', 'hello?'], arrayCustomizer);
c[1].method('exists?')
Upvotes: 1
Reputation: 92481
Proxy is indeed what you're looking for. Take a look at this example:
const array = ['a', 'b', 'c'];
const proxy = new Proxy(array, {
get(target, property) {
console.log(`Proxy get trap, property ${property}`);
return Reflect.get(target, property);
},
});
proxy[1]; // logs "Proxy get trap, property 1"
Whatever you return in the get
trap will be the result of evaluating proxy[index]
, so for example instead of returning Reflect.get(target, property)
you could return some object.
Upvotes: 1