I'm working with JS Proxies for fun and have made decent progress. But currently it's all at a single level. What I would like is to have nested Proxies be returned if I'm making a nested call/access, otherwise just return the object.
// example calls/accesses
data.settings = {fire: true};
data.settings.fire // nested call
// returns true
data.settings // top level call
// returns {fire: true}
// the proxy code
const data = new Proxy({}, {
get: function(target, property, receiver) {
// how to figure out nestedCall?
if (nestedCall) {
return new Proxy(target[property], {
get: function(subTarget, subProperty, subReceiver) {
return 'nonsense, there is nothing nested here';
}.
});
}
else {
return target[property];
}
},
});
Is this even possible?
Is this even possible?
No, it is not possible to distinguish
const val = data.settings.fire; // two accesses
from
const obj = data.settings; // one access
const val = obj.fire; // another access
and return a plain object, instead of a proxy for it, for .settings only in the second case.
Just use a 'set' trap. This 'set' trap example proxies objects as they are being assigned. You can alter the criteria to be more sophisticated as needed.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#A_complete_traps_list_example
const whatICareAbout = ['settings', 'mediaSettings', 'fire', 'video'];
const proxy = {
get: function(obj, prop) {
if(whatICareAbout.includes(prop)) console.log('Get...', prop);
return obj[prop];
},
set: function(obj, prop, value) {
if(typeof value === 'object') {
console.log('Proxy...', prop);
obj[prop] = new Proxy(value, proxy);
} else {
obj[prop] = value;
}
}
};
const p = new Proxy({}, proxy);
p.settings = {fire: true};
p.settings.mediaSettings = {video: false};
console.log(p.settings);
console.log(p.settings.mediaSettings);
Related
I'm currently trying to write a class that defines a generic getter and setter for all properties of the object. The closest thing I have right now is functions named get and set for the class as a whole which takes a property name and returns it if it exists.
I've tried to use the computed property names option for dynamically defining a getter, but I'm getting errors on either giving the getter arguments, or errors about prop being undefined. Is there any way to define a pair that works for both obj.prop and obj['prop'] that doesn't require writing them for every property in a class?
Current code for reference:
class Holder {
constructor(def="the door"){
this.holds=def
}
// Generic getter for every possible property
get (prop) {
if(prop in this){
return this[prop];
} else {
return `No property found named '${prop}'`;
}
}
// Generic setter, performs magic that sets `changed` as well.
set (prop, value) {
if(prop in this){
this[prop] = value;
this.changed = true;
}
}
}
const hodor = new Holder();
console.log(hodor.holds); // Expected: "the door"
console.log(hodor.derp); // Expected: "No property found named 'derp'"
So if you run the code above you'll find it's not actually working. The console.log(hodor.holds) is going directly to the underlying this.holds instance property.
A quick search on StackOverflow led me to this Is it possible to implement dynamic getters/setters in JavaScript?. I've modified your code to use this Proxy approach and this now works correctly:
class Holder {
constructor(def = 'the door') {
this.holds = def;
return new Proxy(this, {
get(target, name, receiver) {
if (name in target) {
return target[name];
} else {
return `No property found named '${name}'`;
}
},
set(target, name, receiver) {
if (name in target) {
target[name] = receiver;
target.changed = true;
}
},
});
}
}
const hodor = new Holder();
console.log(hodor.holds); // Logs: the door
console.log(hodor.derp); // Logs: "No property found named 'derp'"
hodor.holds = 'bar';
console.log(hodor.holds); // Logs "bar"
Is there any way to define a pair that works for both obj.prop and obj['prop'] that doesn't require writing them for every property in a class?
Two options for you:
In the constructor, build the accessors in a loop over an array of the names of the properties you want to have accessors. This is creating them for each property, but not writing them for each property. :-)
Or use a Proxy, but you may not want the overhead. With a Proxy object, you can intercept the get and set actions regardless of what property is being gotten/set.
But in both cases, you'll need a place to put the actual values the accessors access. That could be a WeakMap keyed by Holder instances, or a private property (either truly private or pseudo-private), or something else.
Here's a quick sketch of #1 with a WeakMap:
const holderData = new WeakMap();
class Holder {
constructor(def = "the door") {
// Create an object to hold the values for this instance
holderData.set(this, Object.create(null));
this.def = def;
}
}
for (const name of ["def", "x", "y", "z"]) {
Object.defineProperty(Holder.prototype, name, {
get() {
console.log(`(Getting "${name}")`);
return holderData.get(this)[name];
},
set(value) {
console.log(`(Setting "${name}" to "${value}")`);
holderData.get(this)[name] = value;
}
});
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);
When a Holder instance is no longer reachable, it becomes eligible for garbage collection, and the same happens to the object in the WeakMap.
Here's a quick sketch of #2, also with a WeakMap although you could just use the target object (I think when I wrote this I hadn't had enough coffee yet):
const holderData = new WeakMap();
class Holder {
constructor(def = "the door") {
// Create an object to hold the values for this instance
holderData.set(this, Object.create(null));
// Create the proxy
const proxy = new Proxy(this, {
get(target, propKey, receiver) {
if (typeof propKey === "string") { // Or whatever check you want for
// the properties you want to handle
console.log(`(Getting ${propKey.toString()})`);
return holderData.get(target)[propKey];
}
// Default handling
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
if (typeof propKey === "string") { // Or whatever
console.log(`(Setting "${propKey.toString()}" to "${value}")`);
holderData.get(target)[propKey] = value;
return true;
}
// Default handling
return Reflect.set(target, propKey, receiver, value);
}
});
proxy.def = def;
return proxy;
}
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);
Here's that same thing using the target object to hold the values:
class Holder {
constructor(def = "the door") {
this.def = def;
// Return the proxy
return new Proxy(this, {
get(target, propKey, receiver) {
if (typeof propKey === "string") { // Or whatever check you want for
// the properties you want to handle
console.log(`(Getting ${propKey.toString()})`);
return target[propKey];
}
// Default handling
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
if (typeof propKey === "string") { // Or whatever
console.log(`(Setting "${propKey.toString()}" to "${value}")`);
target[propKey] = value;
return true;
}
// Default handling
return Reflect.set(target, propKey, receiver, value);
}
});
}
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);
Using getter/setter
I'm creating an IIFE like below. It returns getters and setters to an array variable stored internally. I wish to intercept changes made to that array - the console.log is intended to indicate that in the setter below.
const a = (function() {
let arr = [];
return {
get arr() {return arr},
set arr(v) {
console.log("new arr", v);
arr = v;
},
}
})();
This works fine if I completely reassign arr, e.g. a.arr = [1, 2].
But it doesn't intercept changes made to contents of the array, e.g. a.arr.push(3) or a.arr.shift().
Looking for any ideas on how to intercept these content changes.
Using Proxy
This is an alternate attempt using the new Proxy object:
a = (function() {
let details = {
arr: []
}
function onChangeProxy(object, onChange) {
const handler = {
apply: function (target, thisArg, argumentsList) {
onChange(thisArg, argumentsList);
return thisArg[target].apply(this, argumentsList);
},
defineProperty: function (target, property, descriptor) {
Reflect.defineProperty(target, property, descriptor);
onChange(property, descriptor);
return true;
},
deleteProperty: function(target, property) {
Reflect.deleteProperty(target, property);
onChange(property, descriptor);
return;
}
};
return new Proxy(object, handler);
};
return onChangeProxy(details, (p, d) => console.log(p, d));
})();
The problem remains the same. Still unable to observe changes made to the contents of a.arr using anything from a.arr[0] = 1 to a.push(3).
Update: The elegant solution (courtesy Kris Pruden and Sindre Sorhus)
The solution is based on this commit by Kris on Sindre's 'on-change' library.
Explanation of the solution, by Kris:
In the set trap, my goal is to determine if the value provided is a Proxy produced by a previous call to the get trap. If it is such a Proxy, any property access will be intercepted by our own get trap. So, when I access value[proxyTarget] our get trap will be invoked, which is coded to return target when property === proxyTarget (line 46). If the value passed to set is not an on-change-created Proxy, then value[proxyTarget] is undefined.
Full code of the solution:
(object, onChange) => {
let inApply = false;
let changed = false;
function handleChange() {
if (!inApply) {
onChange();
} else if (!changed) {
changed = true;
}
}
const handler = {
get(target, property, receiver) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
const value = Reflect.get(target, property, receiver);
// Preserve invariants
if (descriptor && !descriptor.configurable) {
if (descriptor.set && !descriptor.get) {
return undefined;
}
if (descriptor.writable === false) {
return value;
}
}
try {
return new Proxy(value, handler);
} catch (_) {
return value;
}
},
set(target, property, value) {
const result = Reflect.set(target, property, value);
handleChange();
return result;
},
defineProperty(target, property, descriptor) {
const result = Reflect.defineProperty(target, property, descriptor);
handleChange();
return result;
},
deleteProperty(target, property) {
const result = Reflect.deleteProperty(target, property);
handleChange();
return result;
},
apply(target, thisArg, argumentsList) {
if (!inApply) {
inApply = true;
const result = Reflect.apply(target, thisArg, argumentsList);
if (changed) {
onChange();
}
inApply = false;
changed = false;
return result;
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
return new Proxy(object, handler);
};
This has solved my problem, without resorting to the hack of checking for array modifying methods.
Original solution:
I've sorted this, for now, with help from David Walsh's post here. It's still ugly, but works for now.
Updated the onChanged Proxy maker with a recursive-ish get trap.
get: function (target, property, receiver) {
let retval;
try {
retval = new Proxy(target[property], handler);
} catch (err) {
retval = Reflect.get(target, property, receiver);
}
if (mutators.includes(property))
onChange(target, property, receiver);
return retval;
},
Also added a list of functions to check the get trap against (the ugly, hacky bit):
const mutators = [
"push",
"pop",
"shift",
"unshift",
"splice",
"reverse",
"fill",
"sort"
]
This seems to be working in my testing so far.
Thanks for pointing in the correct direction.
I have a class that implements the XMLHttpRequest interface. Depending on the URL passed to open(), I can determine whether to use the default XMLHttpRequest or my custom implementation. My idea is to use a proxy to do this:
let xhr = new XHRProxy();
xhr.open('GET', 'http://blah'); // Decide here depending on URL
I did some tests using the ES6 Proxy, which seems promising, but unfortunately the proxy target cannot be modified after constructing the Proxy:
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
}
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
// FIXME: This doesn't work because a Proxy's target is not exposed AFAIK
receiver.target = bar;
return function() {};
} else {
return target[property];
}
}
}
var proxy = new Proxy(foo, handler);
console.log(proxy.name()); // foo
proxy.switchToBar();
console.log(proxy.name()); // foo :(
I think I can accomplish what I want by not setting a target at all - instead defining all traps to delegate to the desired object - but I'm hoping for a simpler solution.
Here's a go at "defining all traps to delegate to desired object"
(function () {
let mutableTarget;
let mutableHandler;
function setTarget(target) {
if (!(target instanceof Object)) {
throw new Error(`Target "${target}" is not an object`);
}
mutableTarget = target;
}
function setHandler(handler) {
Object.keys(handler).forEach(key => {
const value = handler[key];
if (typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
if (!Reflect[key]) {
throw new Error(`Trap "${key}: ${value}" is not a valid trap`);
}
});
mutableHandler = handler;
}
function mutableProxyFactory() {
setTarget(() => {});
setHandler(Reflect);
// Dynamically forward all the traps to the associated methods on the mutable handler
const handler = new Proxy({}, {
get(target, property) {
return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]);
}
});
return {
setTarget,
setHandler,
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(mutableTarget, handler)
};
}
window.mutableProxyFactory = mutableProxyFactory;
})();
const {
proxy,
setTarget
} = mutableProxyFactory();
setTarget(() => 0);
console.log(`returns: ${proxy()}`);
setTarget({ val: 1 });
console.log(`val is: ${proxy.val}`);
setTarget({ val: 2 });
console.log(`val is: ${proxy.val}`);
setTarget(() => 3);
console.log(`returns: ${proxy()}`);
I feel like there must be some reason this isn't supported out of the box, but I don't have enough information to comment on that further.
After hacking on this for a while, I've observed a few things. It seems the original target that the proxy constructor is called with is treated as part of the proxy's identity regardless. Setting the original target to a plain object and swapping the target to a function later raises an error, when the proxy is called. There are definitely some rough edges to this, so use with caution.
I took John's answer and improved it (I hope):
const mutableProxyFactory = (mutableTarget, mutableHandler = Reflect) => ({
setTarget(target) {
new Proxy(target, {}); // test target validity
mutableTarget = target;
},
setHandler(handler) {
new Proxy({}, handler); // test handler validity
Object.keys(handler).forEach(key => {
const value = handler[key];
if (Reflect[key] && typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
});
mutableHandler = handler;
},
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(
mutableTarget,
new Proxy({}, {
// Dynamically forward all the traps to the associated methods on the mutable handler
get(target, property) {
return (_target, ...args) => mutableHandler[property].apply(mutableHandler, [mutableTarget, ...args]);
}
}),
)
});
Some important differences:
John's version only has one mutableTarget/Handler variable shared by all results from mutableProxyFactory, so you probably can't call it multiple times.
The handler is allowed to have additional keys, because there's no reason it shouldn't.
Traps in the handler have this bound to the handler, as in a normal Proxy.
The user can give initial values for the handler and target. A value for the target is actually required.
mutableProxyFactory isn't assigned globally to window.
EDIT: here's a similar version but implemented as a TypeScript class. Haven't tested this one.
class MutableProxy<T extends object> {
constructor(private _target: T, private _handler: ProxyHandler<T>) {
this.target = _target;
this.handler = _handler;
}
public get target() {
return this._target;
}
public set target(target: T) {
new Proxy(target, {}); // test target validity
this._target = target;
}
public get handler() {
return this._handler;
}
public set handler(handler: ProxyHandler<T>) {
new Proxy({}, handler); // test handler validity
for (const [key, value] of Object.entries(handler)) {
if (Reflect.has(Reflect, key) && typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
}
this._handler = handler;
}
public get proxy() {
return new Proxy(
this.target,
new Proxy({}, {
// Dynamically forward all the traps to the associated methods on the mutable handler
get(target, property) {
return (_target: T, ...args: any[]) => {
const {handler} = this;
return handler[property].apply(handler, [this.target, ...args]);
};
}
}),
);
}
}
Is it possible to change a Proxy's target?
No, this is not possible. The proxy handler is a quite generic interface already, and by defining all traps to forward the operation to a different handler this is easily achievable. That's why there is no extra method to change the target, the interface is kept minimal. By not making the target changeable, also the shape of the proxy is preserved (e.g. whether it's callable or an array).
How about this? Instead of making foo or bar the target directly, we use a box for our target, and put foo or bar into the box.
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
};
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
target.content = bar;
return function() {};
} else {
return target.content[property];
}
}
};
var box = {content: foo};
var proxy = new Proxy(box, handler);
console.log(proxy.name()); // foo
// Switch over to bar by calling the function
proxy.switchToBar();
// Or, we could do the switch from out here
box.content = bar;
// Either way, we get the same result
console.log(proxy.name()); // bar
In this case, our box is an object with property content. But alternatively, you could use an array with an item at index 0.
Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?
Object.defineProperty( obj, 'prop', {
get: function () {
return val;
}
});
The result should be (for val = 'test'):
obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only
This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
var obj = {};
Object.defineProperty(obj, "prop", {
value: "test",
writable: false
});
As mentioned in the comments, the writable option defaults to false so you can omit it in this case:
Object.defineProperty(obj, "prop", {
value: "test"
});
This is ECMAScript 5 so won't work in older browsers.
In new browsers or node.js it is possible to use Proxy to create read-only object.
var obj = {
prop: 'test'
}
obj = new Proxy(obj ,{
setProperty: function(target, key, value){
if(target.hasOwnProperty(key))
return target[key];
return target[key] = value;
},
get: function(target, key){
return target[key];
},
set: function(target, key, value){
return this.setProperty(target, key, value);
},
defineProperty: function (target, key, desc) {
return this.setProperty(target, key, desc.value);
},
deleteProperty: function(target, key) {
return false;
}
});
You can still assign new properties to that object, and they would be read-only as well.
Example
obj.prop
// > 'test'
obj.prop = 'changed';
obj.prop
// > 'test'
// New value
obj.myValue = 'foo';
obj.myValue = 'bar';
obj.myValue
// > 'foo'
In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.
class SetOnlyOnce {
#innerObj = {}; // private field, not accessible from outside
getCurrentPropertyName(){
const stack = new Error().stack; // probably not really performant method
const name = stack.match(/\[as (\w+)\]/)[1];
return name;
}
getValue(){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] === undefined){
throw new Error('No global param value set for property: ' + key);
}
return this.#innerObj[key];
}
setValue(value){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] !== undefined){
throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
}
this.#innerObj[key] = value;
}
}
class GlobalParams extends SetOnlyOnce {
get couchbaseBucket() { return this.getValue()}
set couchbaseBucket(value){ this.setValue(value)}
get elasticIndex() { return this.getValue()}
set elasticIndex(value){ this.setValue(value)}
}
const _globalParams = new GlobalParams();
_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';
console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)
_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:
var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);
//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));
//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');
//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.
I hope it helps.
I tried and it Works ...
element.readOnly = "readOnly" (then .readonly-> true)
element.readOnly = "" (then .readonly-> false)
I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?
I was thinking on the lines of ...
MyClass.operator.lookup(index)
{
return myArray[index];
}
or am I not looking at the right things.
You can do this with ES6 Proxy (available in all modern browsers)
var handler = {
get: function(target, name) {
return "Hello, " + name;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123
Check details on MDN.
You can't overload operators in JavaScript.
It was proposed for ECMAScript 4 but rejected.
I don't think you'll see it anytime soon.
The simple answer is that JavaScript allows access to children of an Object via the square brackets.
So you could define your class:
MyClass = function(){
// Set some defaults that belong to the class via dot syntax or array syntax.
this.some_property = 'my value is a string';
this['another_property'] = 'i am also a string';
this[0] = 1;
};
You will then be able to access the members on any instances of your class with either syntax.
foo = new MyClass();
foo.some_property; // Returns 'my value is a string'
foo['some_property']; // Returns 'my value is a string'
foo.another_property; // Returns 'i am also a string'
foo['another_property']; // Also returns 'i am also a string'
foo.0; // Syntax Error
foo[0]; // Returns 1
foo['0']; // Returns 1
Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:
var handler = {
get: function(target, name) {
if (name in target) {
return target[name];
}
if (name == 'length') {
return Infinity;
}
return name * name;
}
};
var p = new Proxy({}, handler);
p[4]; //returns 16, which is the square of 4.
We can proxy get | set methods directly. Inspired by this.
class Foo {
constructor(v) {
this.data = v
return new Proxy(this, {
get: (obj, key) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key]
else
return obj[key]
},
set: (obj, key, value) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key] = value
else
return obj[key] = value
}
})
}
}
var foo = new Foo([])
foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:
var obj = {
get attr() { alert("Getter called!"); return 1; },
set attr(value) { alert("Setter called!"); return value; }
};
obj.attr = 123;
The same for IE8+:
Object.defineProperty("attr", {
get: function() { alert("Getter called!"); return 1; },
set: function(value) { alert("Setter called!"); return value; }
});
For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.
The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.
one sneaky way to do this is by extending the language itself.
step 1
define a custom indexing convention, let's call it, "[]".
var MyClass = function MyClass(n) {
this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
...
var foo = new MyClass(1024);
console.log(foo["[]"](0));
step 2
define a new eval implementation. (don't do this this way, but it's a proof of concept).
var MyClass = function MyClass(length, defaultValue) {
this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));
var mini_eval = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = eval(values[0]);
var i = eval(values[2]);
// higher priority than []
if (target.hasOwnProperty('[]')) {
return target['[]'](i);
} else {
return target[i];
}
return eval(values[0])();
} else {
return undefined;
}
} else {
return undefined;
}
} else {
return undefined;
}
};
mini_eval("foo[33]");
the above won't work for more complex indexes but it can be with stronger parsing.
alternative:
instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.
var compile = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = values[0];
var i = values[2];
// higher priority than []
return `
(${target}['[]'])
? ${target}['[]'](${i})
: ${target}[${i}]`
} else {
return 'undefined';
}
} else {
return 'undefined';
}
} else {
return 'undefined';
}
};
var result = compile("foo[0]");
console.log(result);
console.log(eval(result));
You need to use Proxy as explained, but it can ultimately be integrated into a class constructor
return new Proxy(this, {
set: function( target, name, value ) {
...}};
with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]
So you're hoping to do something like
var whatever = MyClassInstance[4];
?
If so, simple answer is that Javascript does not currently support operator overloading.
Have a look at Symbol.iterator. You can implement a user-defined ##iterator method to make any object iterable.
The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.
Example:
class MyClass {
constructor () {
this._array = [data]
}
*[Symbol.iterator] () {
for (let i=0, n=this._array.length; i<n; i++) {
yield this._array[i]
}
}
}
const c = new MyClass()
for (const element of [...c]) {
// do something with element
}