Extending Array / Proxy in ES6 correctly? - javascript

Currently trying to make a custom implementation of Array / Object (Would end up being pretty similar i suppose) and have stumbled upon an issue that is driving me crazy.
As you can see, b is only an instanceOf Array, even though its created from custom class CachedArray, thus my custom function testPush is not defined, and i cant for the sake of everything find what is the issue.
Using Nodejs 6
function _setKey(target, key, value) {
console.log('Setting value', key, 'to', value);
target[key] = value;
return true;
}
class ExtendableProxy {
constructor(a, b) {
return new Proxy(a, b);
}
}
class CachedArray extends ExtendableProxy {
constructor(redis, options) {
let RawArray = [];
super(RawArray, {
set: _setKey
});
this._rawArray = RawArray;
this.redis = redis;
this.options = options;
}
testPush() {
this.push('Its me');
}
}
var b = new CachedArray();
console.log('b instanceof CachedArray', b instanceof CachedArray); //false
console.log('b instanceof ExtendableProxy', b instanceof ExtendableProxy); //false
console.log('b instanceof Proxy', b instanceof Proxy); //false
console.log('b instanceof Array', b instanceof Array); //true
b.push('Hello.'); //Works just fine, _setKey is called and executed correctly
b.testPush(); //TypeError: b.testPush is not a function

Is there an alternative to what i am trying to achieve? Essentially i need an array with some extra functions of mine, which however has a Proxy connected to it, so that i can further process any writes happening to the instance of my class (So, the Array)
Well, the proxy has a handler that allows you to hook into every kind of interaction with the proxied object. So you should use that to inject any additional methods you want to add to the proxied object. For example, just provide the get so it returns a custom function instead of relaying the call back to the target:
function createCachedArray(arr) {
const customFunctions = {
testPush() {
this.push('It’s me');
},
};
return new Proxy(arr, {
set (target, property, value) {
console.log(`Setting value ${property} to ${value}`);
target[property] = value;
return true;
},
has (target, property) {
return property in customFunctions || property in target;
},
get (target, property) {
if (typeof property === 'string' && property in customFunctions) {
return customFunctions[property].bind(target);
}
else {
return target[property];
}
},
});
}
let a = [1, 2, 3];
let b = createCachedArray(a);
b.push('foo');
b.testPush();
for (const x of b) {
console.log(x);
}

Related

Is there any way to intercept methods triggered with [] in js? [duplicate]

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
}

Property accessors vs. Map set method

Is there a way for the following code to either
raise an error on m[k] = v or
make the call m[k] = v automatically translate to m.set(k, v)
I would prefer solution 1. if possible.
// Imagine I write that somewhere
const m = new Map();
m.set("a", "alice");
// And at some point someone else write:
m["b"] = "bob"; // m = Map { 'a' => 'alice', b: 'bob' }
// Expecting "bob" to appear here:
for (const [k, v] of m){
console.log(k, v);
}
Note that when I say someone else, this other person could simply be me in the near future. Ideally I would like to have a solution that only modifies the instantiation const m = new Map(). For example to something like const m = safe(new Map()).
To just prevent properties being added you can use:
Object.freeze(map);
However, that won't throw an error. To be able to throw an error on a property access you have to use a Proxy, however as that does not work on Maps directly (as they got internal properties that do not get reflected through the Proxy) you'd have to mirror all methods in an object, and use the Proxy on that:
function safe(map) {
return new Proxy({
get(k) { return map.get(k); },
set(k, v) { return map.set(k, v); }
// .... others
}, {
set() { throw new Error("property setter on Map"); }
});
}
You could also trap the Proxies getter to directly link to the Map (not sure about side effects though):
function safe(map) {
return new Proxy({ }, {
set() { throw new Error("property setter on Map"); },
get(target, prop, receiver) {
return typeof map[prop] === "function" ? map[prop].bind(map) : map[prop];
}
});
}
you can use proxy
let m = new Map();
m["a"] = "alice";
m = new Proxy(m, {
set(target, name, receiver) { throw "Setting values is forbidden!"; }
});
console.log(m["a"]); // you can read value
m["b"] = "bob"; // setting value will throw exception
You can use the Proxy object. Instantiate it with the first argument being an instantiation of whatever Map you want.
const m = new Proxy(new Map(), {
get(target, name, receiver) {
return target.get(name);
},
set(target, name, receiver) {
// 1. Comment in to allow error to be thrown.
// throw new Error(`Cannot set property for ${name}`);
// 2. Comment in to allow setting via bracket syntax.
// target.set(name, receiver);
}
});

Transform a Javascript object into a proxy (and not its reference)

I can take a Javascript object o and create a new Proxy object from it:
let p = new Proxy(object, { ... })
But is there a way to mutate an existing object reference to track changes on the original object? In particular, is there a way I can track the addition of new keys on the object from exterior sources?
The Proxy spec supports defining a proxy on the prototype of an object as a means for inspecting actions on that object when they do not exist on the instance. While this isn't full parity with .watch() it does allow for your mentioned use case of knowing when new properties are added. Here is an example, with comments about caveats...
// assuming some existing property you didn't create...
const t = { existing: true };
// proxy a new prototype for that object...
const ctr = {};
Object.setPrototypeOf(t, new Proxy(ctr, {
get(target, key) {
console.log('icu get');
return Reflect.get(target, key) || ctr[key];
},
set(target, key, val) {
console.log('icu set');
// setting this container object instead of t keeps t clean,
// and allows get access to that property to continue being
// intercepted by the proxy
Reflect.set(ctr, key, val);
return true;
},
deleteProperty(target, key) {
console.log('icu delete');
delete ctr[key];
return true;
}
}));
// existing properties don't work
console.log('existing');
t.existing; // <nothing>
t.existing = false; // <nothing>
// new properties work
console.log('new');
t.test; // icu get
t.test = 4; // icu set
console.log(t.test); // icu get
// 4
// but this doesn't work (and I think it should be a bug)
console.log('delete');
delete t.test; // icu get
// <missing icu delete>
console.log(t.test); // 4
Just create the object first and keep a reference to it before creating its Proxy.
Now you can modify either of them (the original object or its Proxy) and the other will also receive the changes unless you prevent them on the Proxy:
const o = {};
const p = new Proxy(o, {
set: function(obj, prop, value) {
if (prop === 'd') {
return false;
}
obj[prop] = value;
return true;
},
});
// These operations are forwarded to the target object o:
p.a = 0;
p.b = 1;
// This one is prevented by the Proxy:
p.d = true;
// Both will have two properties, a and b:
console.log(o);
// You can also mutate the original object o and the Proxy will also get those changes:
o.c = false;
// Note that now the Proxy setter is not called, so you can do:
o.d = true;
// But the Proxy still gets the change:
console.log(p);
If you want to be notified when a new property is added, deleted or modified on an object without the possiblity that the original reference is used to mutate the original object directly, the only option you have is to create that object directly as a Proxy or overwrite the original one:
// Created from an empty object without a reference to it:
// const p = new Proxy({}, { ... });
// Overwrite the original reference:
let myObject = { a: 1, b: 2 };
myObject = new Proxy(myObject, {
set: function(obj, prop, value) {
if (prop in obj) {
console.log(`Property ${ prop } updated: ${ value }`);
} else {
console.log(`Property ${ prop } created: ${ value }`);
}
obj[prop] = value;
return true;
},
deleteProperty(obj, prop) {
console.log(`Property ${ prop } deleted`);
delete obj[prop];
}
});
// Now there's no way to access the original object we
// passed in as the Proxy's target!
myObject.a = true;
myObject.a = false;
delete myObject.a;
There used to be an Object.prototype.watch(), but it has been deprecated.

Creating custom JS syntax / shorthand

I would like to create some custom JS syntax. Is there a way I can write a method so that the following would work:
var someDict = {"abc": 1, "efg": 2}
someDict.keys
someDict.values
instead of:
Object.keys(someDict)
Object.values(someDict)
In Swift this sort of thing can be done via extensions, I'm just wondering if there's a way in JS.
You can create an object with getters:
class ObjWithKeyValGetters {
constructor(obj) {
Object.assign(this, obj);
}
get keys() {
return Object.keys(this);
}
get values() {
return Object.values(this);
}
}
const myObj = new ObjWithKeyValGetters({"abc": 1, "efg": 2});
console.log(myObj.keys);
console.log(myObj.values);
Maybe you are looking for prototype . you can add new methods to Object with prototype like :
var current = Object.prototype.valueOf;
// Since my property "-prop-value" is cross-cutting and isn't always
// on the same prototype chain, I want to modify Object.prototype:
Object.prototype.valueOf = function() {
if (this.hasOwnProperty('-prop-value')) {
return this['-prop-value'];
} else {
// It doesn't look like one of my objects, so let's fall back on
// the default behavior by reproducing the current behavior as best we can.
// The apply behaves like "super" in some other languages.
// Even though valueOf() doesn't take arguments, some other hook may.
return current.apply(this, arguments);
}
}
as modified MDN pollyfill for Object.keys:
Object.prototype.pollufillKeys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({
toString: null
}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [],
prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
}());

Comparison class constructor parameter between Typescript and Javascript ES6, ES.NEXT

I've been studying TypeScript as the requirement for Angular2/5 and I encountered some doubts.
A couple months ago I also upgraded my knowledge of JS ES6 and so on.
I'm quite sure that I'm not in wrong but for a full understanding of TS I'll ask you anyway.
This is the code that you can find here:
https://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties
{
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly classAttr: string) { ... } // name attribute
}
console.log( (new Octopus('spicylemoned')).classAttr ); // It works
}
Is there a way in recent JS updates to define attributes inside class' constructor in vanilla like so in TS? (w/o implicitly assigning it through this instance)
{
class Test{
constructor({ passedVar : classAttr } ) { ... };
};
console.log( (new Test({ passedVar : 'sweety' })).classAttr );
//it doesnt work
}
In JS there isn't any similar syntax. The easiest way to do this is to use Object.assign():
class Test {
constructor({ passedVar }) {
Object.assign(this, { classAttr: passedVar });
}
}
console.log(new Test({ passedVar: 'sweety' }).classAttr);
This way is better if you have many attributes; if there's only one, you can just assign in to this: this.classAttr = passedVar.
I thought of a very contrived way of passing an object initializer to a class using a custom Proxy. It may seem verbose, but the Proxy can be re-used for as many class definitions as you want:
// mem initializer list
const list = (() => {
let props = new WeakMap()
// this: class instance
// o: initializer object
function initialize(o = {}) {
if (props.has(this)) {
return this[props.get(this)] = o;
}
return new Proxy(o, {
get: (o, p) => (props.set(this, p), this[p] = o[p])
});
}
// .call.bind() allows initialize(this, o)
// to pass a context and one argument
// instead of two arguments
return initialize.call.bind(initialize);
})();
// example usage
class Test {
constructor (o, { classAttr = list(this, 'foo'), undefinedParameter = list(this, 'bar') } = list(this, o)) {
/* ... */
}
}
console.log(new Test({ classAttr: 'sweety', excessParameter: 'oops' }));
console.log(new Test());
Notice that undefinedParameter is always initialized with its default value 'bar', while the excessParameter is never added to the class instance.
Note: this is by no means a performant solution. It is a pure gimmick, but demonstrates that it is possible to somewhat implicitly initialize a class instance.
A downside to using this approach is that there is no way to intercept default parameters like
{ classAttr = 'defaultValue' }
so instead you have to use the somewhat unsavory syntax
{ classAttr = list(this, 'defaultValue') }
in order to provide default parameters.
If you are extending another class, you must initialize the instance with a Proxy returned by list(super(), o):
const list = (() => {
let props = new WeakMap()
function initialize(o = {}) {
if (props.has(this)) {
return this[props.get(this)] = o;
}
return new Proxy(o, {
get: (o, p) => (props.set(this, p), this[p] = o[p])
});
}
return initialize.call.bind(initialize);
})();
// example usage
class Test extends Object {
constructor (o, { classAttr = list(this, 'foo'), undefinedParameter = list(this, 'bar') } = list(super(), o)) {
/* ... */
}
}
console.log(new Test({ classAttr: 'sweety', excessParameter: 'oops' }));
console.log(new Test());
The syntax for an ES6 class forbids the assignment of properties directly on a prototype object within the class definition.
You are allowed to use a getter to achieve the same effect. Only defining a getter will also make it readonly.
However, there is no implicit syntax within vanilla javascript to handle typechecks, meaning this must be done manually using typeof <variable> === '<primitiveType>' or <variable> instanceof <Class> within the function.
Using the Octopus class this will result in the following:
class Octopus{
constructor(classAttr){
if (typeof classAttr !== 'string' && typeof classAttr !== 'object') {
throw new TypeError('classAttr is neither a string nor an object');
}
if (typeof classAttr === 'object') {
Object.assign(this,classAttr);
}
else {
this.classAttr = classAttr;
}
}
//readonly, because there is no setter
get numberOfLegs(){
return 8;
}
}
let octopus = new Octopus('bigEyed');
console.log(octopus.numberOfLegs); // 8
console.log(octopus.classAttr); // bigEyed

Categories

Resources