is there any chance to get the outer scope in Proxy? - javascript

I am Proxing an object in a custom class and I'd like to get access to the methods and properties of that same class within my Proxy Object. Is it possible?
I thought there is a way to bind the context but it didn't work for me neight with apply, call nor bind.
Any suggestions would be appreciated!
class MyClass {
constructor() {
this.state = new Proxy(this.initialState, {
set(target, prop, value) {
// some logic goes here
}
})
}
methodIneedToReach() {}
}
I need it to structure the code and prevent the mess.

Either store the value of this in a variable called that for example and use that.methodIneedToReach inside the set method, or, better yet, use an arrow function for set. Since arrow functions don't have their own this keyword, they will use the surrounding one which is, in this case, the instance of your class:
class MyClass {
constructor() {
this.state = new Proxy(this.initialState, {
set: (target, prop, value) => { // set is an arrow function
this.methodIneedToReach(); // works because 'this' inside here points to your class
}
})
}
methodIneedToReach() {}
}
Demo:
class MyClass {
constructor() {
this.initialState = { message: "Hello World!" };
this.state = new Proxy(this.initialState, {
set: (target, prop, value) => {
this.methodIneedToReach();
}
})
}
methodIneedToReach(value) {
console.log("'methodIneedToReach' is called");
}
}
let inst = new MyClass();
inst.state.message = "Bye world!";

Related

nested properties on a class instance

I need to build a structure (i need to do it for jestjs, in order to stub a module) that allows me to access a method in the following way:
// I make an instance of a class
const myTest = new Test()
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll()
A basic class would look like
class Test2 {
constructor(){}
//getter
getAll(){
return 'Salutes'
}
}
And I can access getAll()
const myTest2 = new Test()
const result = myTest.getAll()
//in this way result contains a string (*salutes*)
So how can I add more properties in the middle? I found something, but in typescript... I need to do it in javascript
Assuming you need exactly the structure you've specified, in Test you'd have to create the object and have any methods you need be arrow functions or bound functions (if they need to access information from the Test instance; your example didn't so it wouldn't matter whether they had the right this). For instance:
class Test {
constructor() {
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
return "Salutes";
},
getAll2() { // Also works
return "Salutes";
},
getAll3: function() { // Also works
return "Salutes";
}
}
}
}
}
}
const myTest = new Test();
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
console.log(myTest.prop1.prop2.prop3.getAll2());
console.log(myTest.prop1.prop2.prop3.getAll3());
Example of using information on the Test instance (it's the same except for the constructor parameter and message property):
class Test {
constructor(message) {
this.message = message;
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
// `this` refers to the `Test` instance because
// this is an arrow function
return this.message;
}
// `getAll2` and `getAll3` wouldn't work because
// they would get `this` based on how they're called
// (it would be the `this.prop1.prop2.prop3` object).
}
}
}
}
}
// I make an instance of a class
const myTest = new Test("Salutes encore!");
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
If you want to achieve it without modifying the Test2 class, you can use Proxy class and define custom handler for get method:
class Test2 {
constructor(){}
getAll(){
return 'Salutes'
}
}
let instance = new Test2();
const handler = {
get: function(target, prop, receiver) {
if (['prop1', 'prop2', 'prop3'].includes(prop)) {
return receiver;
}
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(instance, handler);
console.log(proxy.prop1.prop2.prop3.getAll());
console.log(proxy.prop7);
console.log(proxy.getAll());

How to inject custom function to class in typescript

I would like to inject a custom function into my class from variable functions, how can I do this?
My code is:
const functions = {
foo: () => console.log('foo')
}
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
function Base() {
class BaseClass extends ParentBase { }
BaseClass.foo = functions['foo']
return BaseClass;
}
const Class = Base();
const instance = new Class();
instance.parentHello() // it work
instance.foo() // foo is not a function
You could directly declare a new method in the class declaration:
class BaseClass extends ParentBase {
foo() {functions["foo"](); }
}
If the method name should be dynamic, you can also use computed method names:
class BaseClass extends ParentBase {
[window.location]() {functions["foo"](); }
}
If you really want to "dynamically inject" a new method (e.g. conditionally, in a loop, or some other strange construct), you'd have to write into BaseClass.prototype, which is the object BaseClass instances will inherit from:
BaseClass.prototype.foo = functions["foo"];
This will however be an enumerable property, to create a non enumerable one, use Object.defineProperty:
Object.defineProperty(BaseClass.prototype, "foo", {
value: functions["foo"],
/* enumerable: false,
writable: false */
});
Here is a small showcase:
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
function Base() {
class BaseClass extends ParentBase {
instanceMethod() { console.log("BaseClass.prototoype.test", this); };
static classMethod() { console.log("BaseClass.classMethod", this); };
}
BaseClass.classMethod2 = function() { console.log("BaseClass.classMethod2", this); };
BaseClass.prototype.instanceMethod2 = function() { console.log("BaseClass.prototype.instanceMethod2", this); };
Object.defineProperty(BaseClass.prototype, "instanceMethod3", { value() { console.log("BaseClass.prototype.instanceMethod3", this); } });
return BaseClass;
}
const Class = Base();
const instance = new Class();
Class.classMethod();
Class.classMethod2();
instance.instanceMethod();
instance.instanceMethod2();
instance.instanceMethod3();
You have 2 places to access properties:
Instance based
Class Based
What you have is adding to class but you are trying to access using instance.
What you are trying to achieve is called Mixins. These are partial utility functions list that can be shared across multiple classes.
To inject properties to instances, you can either use constructor based approach or you can inject them to prototype
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
const mixin = {
foo: function() { console.log('foo') },
bar: function() { console.log('Bar') },
fooBar: function() { console.log('foo-bar') }
}
function Base() {
class BaseClass extends ParentBase {}
Object.assign(BaseClass.prototype, mixin)
return BaseClass;
}
const Class = Base();
const instance = new Class();
instance.parentHello() // it work
instance.foo() // foo is not a function
instance.fooBar()
You should also know that functions/ properties added on prototype are shared across instances. So its advice to have function but not variables as it will introduce side-effect

Can I call a function within my class constructor that takes the same argument as the constructor?

There are loads of questions that talk about constructors and functions but none seem to answer this exact question.
I'm aware that there are problems with this "this" keyword in JS but I'm not confident I know how to get around those limitations.
I've defined a class that takes an object as a constructor argument, which then loops over the object and assigns them to the class properties.
I then want to extend those classes and call the same constructor, but altered slightly. My goal is to have a function, init(obj) that is called by each constructor.
class Thing {
constructor(obj) {
init(obj)
}
}
That's my goal at any rate. I will have a large number of classes that I want to extend. This is my current, redundant setup for the first four classes.
class Thing {
constructor(obj) {
for(let i in obj) this[i]=obj[i];
if(!Data[obj.type]) Data[obj.type] = [];
if(Data[obj.type]) Data[obj.type].push(this);
}
}
class Role extends Thing {
constructor(obj) {
super();
for(let i in obj) this[i]=obj[i];
if(!Data[obj.type]) Data[obj.type] = [];
if(Data[obj.type]) Data[obj.type].push(this);
}
}
So my question: how would I go about making the constructor less repetitive?
This might help you.
var Data = {}
class Thing {
constructor(obj) {
this._setFields(obj)
}
// you can overwrite this function in child classes
_setFields(obj) {
for (let i in obj) this[i] = obj[i]
this._setType(obj.type)
}
// you can overwrite this function in child classes
_setType(type) {
if (!Data[type]) {
Data[type] = [this]
} else {
Data[type].push(this)
}
}
}
class Role extends Thing {
constructor(obj) {
super(obj)
}
}
const r1 = new Role({ type: 'role', id: '1' })
const r2 = new Role({ type: 'role', id: '2' })
const t1 = new Thing({ type: 'ting', id: '10' })
console.log(Data)

Unable to trap accessor calls on customElements using Proxy?

I'm registering some custom elements using customElements.define and would like to automatically set up traps on member accessors so that I can emit events when they change
class State extends HTMLElement {
public someValue = 1;
public constructor() {
super();
console.log('State constructor');
}
}
const oProxy = new Proxy(State, {
get(target, prop: string) {
console.log(`GET trap ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop: string, value: any) {
console.log(`SET trap ${prop}`);
return Reflect.set(target, prop, value);
}
});
customElements.define('my-state', oProxy);
const oStateEl = document.querySelector('my-state');
console.log(oStateEl.someValue);
console.log(oStateEl.someValue = 2);
console.log(oStateEl.someValue);
My browser doesn't seem to have a problem with the above code and I can see some trap output as the element is set up
GET trap prototype
GET trap disabledFeatures
GET trap formAssociated
GET trap prototype
But when I manually get/set values the traps aren't triggered.
Is this even possible?
What I ended up doing was moving all member variable values to a private object and dynamically defining a getter/setter for each as soon as the custom element was mounted on the DOM like so...
//
class State extends HTMLElement {
protected _data: object = {}
public connectedCallback() {
// Loop over member vars
Object.getOwnPropertyNames(this).forEach(sPropertyKey => {
// Ignore private
if(sPropertyKey.startsWith('_')) {
return;
}
// Copy member var to data object
Reflect.set(this._data, sPropertyKey, Reflect.get(this, sPropertyKey));
// Remove member var
Reflect.deleteProperty(this, sPropertyKey);
// Define getter/setter to access data object
Object.defineProperty(this, sPropertyKey, {
set: function(mValue: any) {
console.log(`setting ${sPropertyKey}`);
Reflect.set(this._data, sPropertyKey, mValue);
},
get: function() {
return this._data[sPropertyKey];
}
});
});
}
}
//
class SubState extends State {
public foobar = 'foobar_val';
public flipflop = 'flipflop_val';
public SubStateMethod() { }
}
//
window.customElements.define('sub-state', SubState);
//
const oState = document.querySelector('sub-state') as SubState;
oState.foobar = 'foobar_new_val';
That way I can still get/set values on the object as normal, typescript is happy that the member variables exist, and I can trigger custom events when members are accessed - all while allowing custom elements to exist within the markup at DOM ready

A way to retrieve the context without passing `.this` as parameter

Here is a little background.
So I have my parent class. Constructor receives two objects: the first one is the class where the instance of the parent object was created (using others classes which are inheriting parent class).
The second object are just some parameters
parent.js
class ParentClass {
constructor(nativeObject, params) {
this.nativeObject = nativeObject;
this.param1 = params.param1;
this.param2 = params.param2;
}
// some logic here
}
module.exports = { ParentClass };
child.js
class ChildClass extends ParentClass {
// some logic here
}
module.exports = { ChildClass };
I'm using the child class in the other class methods for creating it. I can have multiple files like below:
usingclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(this, opts);
}
}
module.exports = { MyCoolClass };
anotherclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'thatAstringToo';
param2: 123;
}
class AnotherCoolClass{
alsoCreatesChild() {
return new ChildClass(this, opts);
}
}
module.exports = { AnotherCoolClass};
I can create multiple instances of the ChildClass in a different methods within one class:
thirdexample.js
let { ChildClass } = require('path/to/my/child/class');
let opts1 = {
param1: 'aStringAgain';
param2: 27;
}
let opts2 = {
param1: 'ABC';
param2: 33;
}
class ClassAgain{
firstMethod() {
return new ChildClass(this, opts1);
}
secondMethod() {
return new ChildClass(this, opts2);
}
}
module.exports = { ClassAgain };
The reason why I'm passing .this because I'm working with it in ParentClass
e.g for logging console.log(`I know this was created in ${this.nativeObject .constructor.name}`);
What I'm looking for is the way to get rid of passing .this every time I'm creating a new instance of the ChildClass
Reason behind this is that in one file (class) I can create up to 10+ instances of the ChildClass.
In the previous question I already got an answer which helped me to make the code more structured and more easy to maintain:
Instead of:
createChild() {
return new ChildClass(this, param1, param2, param3, paramN);
}
I'm doing:
createChild() {
return new ChildClass(this, paramObj);
}
So now I can store all my objects with parameters at the top of the file (of even separate file) and easily find and change it if needed.
...
Now I'm trying to find a way to get rid of .this every time I'm creating ChildClass
Is there any other way how ParentClass can know where does this ChildClass instances "lives"?
I'm trying to figure out can I do something using .apply or .call but seems like this is not a droids I'm looking for
UPDATE:
Since I have zero progress on this and I even got a comment that most likely this is not possible let's assume for now that I need a name of the class that is creating instance of the ChildClass only for logging.
How can I retrieve the name of the class without passing it every time when creating a ChildClass?
let opts = {
className: 'MyCoolClass'
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(opts);
}
}
module.exports = { MyCoolClass };
The problem is that I will have to set className: 'MyCoolClass' in each version of opts again and again and again... Just repeat the same for 10-15 times
You can just instantiate your classes using Reflect.construct.
class ParentClass {
constructor (nat, x) {
this.nat = nat
this.x = x
}
log () {
console.log('from ', this.nat.constructor.name, this.x)
}
}
class ChildClass extends ParentClass{}
class ClassAgain{
firstMethod () {
return this.makeInstance({ a: 1 })
}
secondMethod () {
return this.makeInstance({ b: 2 })
}
makeInstance (params) {
return Reflect.construct(ChildClass, [this, params])
}
}
const ca = new ClassAgain()
ca.firstMethod().log()
ca.secondMethod().log()

Categories