JavaScript binding Proxy getters/setters to a specific context - javascript

This might be a weird use case with no solution, but I would be happy to hear any answers on how to solve it.
Question:
I have a globally defined proxy:
this.appState = {}
global.globalState = new Proxy(this.appState, {
get: (target, prop, receiver) => {
console.log(this) // #3
return Reflect.get(...arguments)
},
set: (obj, prop, value) => {
console.log(this) // #2
obj[prop] = value
return true
}
})
And somewhere else I have a class:
export default class MyClass {
constructor() {
console.log(this) // #1
global.globalState["greet"] = "hi"
console.log(this.globalState["greet"])
}
}
Is it possible to bind this from the class constructor to be also this in getter and setter of the proxy? So basically, the context of the class constructor needs to be accessed from inside of the proxy getter and setter.

If your question is wether the line
global.globalState["greet"] = "hi"
can be somehow modified to change the this in the Proxies get method, then the answer is no. The arrow function is lexically bound (so its this can't change between different calls), and even if you would use a regular function instead there is still no way to call it by assigning a property, as the function is called by the runtime, and as Proxies are transparent, there is no way to influence that at all (you don't even know that the line calls a setter).
To dynamically change this you could use something like:
let context = null;
// inside the getter
(function thisInjection() {
}).call(context);
however that would require modifying the Proxies code, and in that case replacing this with context would be way easier.

Related

How can I take this inline function and convert it to a method while retaining access to this?

I have a JavaScript class that has this kind of structure:
class MyClass () {
myMethod() {
myCallback = (response) => {
// Do a bunch of stuff by referencing and altering properties in this
}
apiFunction(this.CLASS_PROP, myCallback);
}
}
myMethod is already pretty long, and the contents of myCallback make it even longer, so I tried converting it to this structure:
class MyClass () {
myMethod() {
apiFunction(this.CLASS_PROP, this.myCallback);
}
myCallback = (response) => {
// Do a bunch of stuff by referencing and altering properties in this
}
}
Whereas the first version works fine, the second one loses its reference to the MyClass instance and instead this is pointing to, I think, the call is the API that actually called it. I'm not positive as to why, but my theory is that "lexical context" doesn't mean where the function is defined, but where it's called.
But my question is, is there a way to break that function out of being inside of myMethod into an instance method of MyClass?
There are many ways to write that, one of them being
class MyClass {
myMethod() {
apiFunction(this.CLASS_PROP, r => this._myCallback(r));
}
_myCallback(response) {
// Do a bunch of stuff by referencing and altering properties in this
}
}
Note that in apiFunction there's an arrow function and _myCallback is an ordinary method (=not arrow). The other way around, like in your code, it won't work.
The underscore in _myCallback indicates a private method (a convention, not a special syntax).

Get instance of class (this) in function inside instance object - typescript/angular

I have a separate object that manages a particular dialog box. Consider the following code. As it is very easy to imagine what the functions do, I'm however unable to access the instance of the class. I tried using the traditional that = this approach.
export class Whatever implements OnInit {
that = this;
dialog = {
data:{},
open:function() {
//way to access 'that' variable
},
close:function() {},
toggle:function() {}
}
//other declarations and functions
}
As my application is scaling, I'm having too many functions inside this service. So i'm trying to club some of these functions into objects, which will make the code much cleaner.
Also if there is any better approach to this, I'd love to know. Thanks.
Best way would be to replace the function(){} with the ES6 arrow functions, which holds your this context like so () => {}.
You can also use functions(){}.bind(this), but it's much better to just use arrow functions. Both will keep your reference to this as expected in the body of the function
You have to use arrow functions to not lose the context(this);
export class Whatever implements OnInit {
dialog = {
data:{},
open:() => {
//'this' will point to Whatever class's instance
},
close:() => {},
toggle:() => {}
}
//other declarations and functions
}
In your code, that isn't a variable, it's a property of the Whatever instance.
Your dialog is also a property of the Whatever instance. In calls to its methods, this will vary depending on how they're called.
To ensure they access the Whatever instance, you can use arrow functions and use this within the functions:
export class Whatever implements OnInit {
dialog = {
data: {},
open: () => {
// use `this` here
// use `this.dialog.data` to acccess the data above
},
close: () => {},
toggle: () => {}
};
//other declarations and functions
}
This works because class fields declared as you've declared them there are effectively initialized as though they were inside your constructor, and within the constructor, this refers to the instance. Arrow functions close over the this of the context where they're created (just like closing over an in-scope variable).

Are simple retrieval and assignment getters and setters useful in JavaScript?

Is there any point in repeating this pattern for every property in JavaScript?
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
I understand that getters/setters can be useful if you're doing additional work in the methods, but recreating the basic functionality here seems like needless repetition. If I instantiate, I can still manipulate the backing property (._myProp) directly, so I feel like I could just as easily leave these out and perform my assignment and access in the more typical, ad-hoc fashion.
I suppose you could argue that defining the interface this way (with the underscore-prefixed property name) signals to users that it's not meant to manipulate the property, but that seems like a flimsy reason for potentially dozens of these.
In compiled languages, it's common for people to do this. This is because in those languages, assigning to a field and invoking a setter may be identical to the programmer, but they compile to two completely different operations.
If I wanted to add a side effect for setting a field in a C# class, for example, and that field was being set directly instead of through a setter? Changing it to a setter would cause some issues. I wouldn't have to rewrite any of the consuming code, but I would have to recompile all of it. This is, of course, a huge problem if your public releases are compiled.
JavaScript is subject to no such considerations, though, so making everything into a getter/setter prematurely is kind of silly. If you see someone doing this, more than likely you're dealing with someone who learned the convention from another language and carried it into JavaScript, without thinking a whole lot about why.
Using an accessor property in the fashion you describe (set and retrieve a "background" data property) is virtually semantically identical to using a data property directly. There are some differences: the accessor property will exist on instance's prototype, rather than on the instance directly (though the instance will have the "background" data property), but this won't really affect anything unless you are doing advanced introspection on your class instances.
The only advantage is ease of modifying the code if you want to introduce more sophisticated accessor behavior in the future. If you forsee a need to add accessor behavior, use this pattern to save yourself time in the future.
Property accessors are useful to provide side effects or change original behaviour:
class Thing {
get myProp() {
console.log('myProp was read');
return this._myProp;
}
set myProp(value) {
if (!value)
throw new Error('myProp cannot be falsy');
this._myProp = value;
}
}
There is no point in myProp getter/setter pure abstraction:
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
If I instantiate, I can still manipulate the backing property
(._myProp) directly,
If private states are what you are looking for you can still use a weak map.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
return prop.get(this)._bar = value;
}
}
}(this))
const f = new Foo;
f.bar = "bar";
f._bar = "_bar";
console.log(f.bar);
console.log(f._bar);
get and setters are also useful when implementing MVC, you can trigger events on property change.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
prop.get(this)._target = new EventTarget
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
prop.get(this)._bar = value;
prop.get(this)._target.dispatchEvent(new CustomEvent('change', {
detail: value
}));
}
addEventListener(event, listener) {
prop.get(this)._target.addEventListener(event, listener)
}
}
}(this))
const f = new Foo;
f.addEventListener('change', function(event) {
console.log("changed!", event.detail);
});
f.bar = "bar";

Scope issue inside objects that are inside objects

I've come across a problem while trying to build a simple jQuery plugin, having to do with scopes I guess.
The problem in short: A class (A) creates an object (B), in which a property (C) is set to one of the class methods (D). How can I access class A's methods not contained inside the object (B) through the property ( C)?
Longer version (code below): I'm declaring an object (lets call it publicMethods) inside the plugin, comprised of a bunch of methods. These methods should be some default methods (declared inside the plugin), or user declared ones if the user has declared her own when initializing the plugin.
The idea is that when the user defines her own custom method, there should be some functions and variables accessible to her (like this.someVar) in that function.
This creates some limitations though.
I want the default methods to have access to some internal functions and variables, not contained inside the object publicMethods. But when I access these methods through the object they are inside, instead of calling them directly, I do not have access to another variables/functions not inside that object.
I'm trying to find a way to let the default methods have access to it's class siblings. I know I can do some conditional statements before calling the method (if it is user defined or not), or even declare a global variable pointing to "this", but I'd rather keep it clean.
var Plugin = function (opt) {
this.settings = $.extend({
"someVar" : "Value",
"someFunc" : null
});
this.anotherVar = "Hello World";
this.setPublic();
this.run();
}
Plugin.prototype = {
setPublic: function() {
this.publicMethods.someFunc = this.someFunc;
if ($.isFunction(this.settings.someFunc)) {
this.publicMethods.someFunc = this.settings.someFunc;
} else {
this.publicMethods.someFunc = this.someFunc;
}
},
someFunc: function(arg) {
return this.anotherVar; // returns type error the second time
},
run: function () {
this.someFunc();
this.publicMethods.someFunc();
}
}
From MDN: Function.prototype.bind():
The bind() method creates a new function that, when called, has its this keyword set to the provided value, [...].
So the following should work:
setPublic: function() {
this.publicMethods.someFunc = this.someFunc.bind(this);
if ($.isFunction(this.settings.someFunc)) {
this.publicMethods.someFunc = this.settings.someFunc.bind(this);
}
// This is redundant anyway:
/* else {
this.publicMethods.someFunc = this.someFunc.bind(this);
}*/
},

Create ES6 class from a function

I'm trying to explore using ES6 classes instead of how we do it currently, using the Function.prototype means. Currently our API looks like:
var myclass = createClass('MyClass', {
test : function() {}
});
We iterate through the object and apply those properties onto the Function that we return, basically a prettier way than to do so that it's more inline with other programming languages of sorts:
function MyClass() {}
MyClass.prototype.test = function() {};
We also cache the class onto an object where name is the key and the function is the value for use throughout our application. The class name can be namespaced so you can have My.Cls and it will split by the period and then cache it onto the manager but it also can be retrieved via window.My.Cls.
Looking into ES6 classes, I don't see how I can keep the createClass function. Would love something like:
function createClass(name, config) {
return class name config;
}
I didn't expect it to work and it doesn't.
Two issues I have here:
How can I create a class using a variable as the class name?
How can I create a class and assign the properties via the config object argument?
Not sure this would be possible. We don't plan on keeping the createClass, we hope to keep it for now and upgrade our legacy "classes". I'd like to start using ES6 classes but not break the whole app for however long it'll take us to fully upgrade.
The only good upgrade route is to refactor the property hashes into proper classes. You can start that work and keep using your hash-based classes in the meantime, which will lighten the requirement to do it all at once.
If you have a limited number of "class" name:config pairs -- which you should for maintainability reasons -- then you can replace createClass with an implementation that does:
class Foo { ... }
class Bar { ... }
let classes = {'Foo': Foo, 'Bar': Bar};
function createClass(name, config) {
if (classes[name]) {
return classes[name];
}
// old impl
}
This will ignore the config if a "real" implementation exists, but keep using the legacy behavior if you haven't replaced the class. If it is, you can implement createClass more like:
function createClass(name, config) {
if (classes[name]) {
return new classes[name](config);
}
// old impl
}
and pass the config arguments into the class ctor. In this case, you may want to filter out function properties (methods) first, as the class probably implements them already. Something like:
function createClass(name, config) {
if (classes[name]) {
let fields = Object.keys(config).filter(key => {
return typeof config[key] !== 'function';
}).map(key => config[key]);
return new classes[name](fields);
}
// old impl
}

Categories