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";
Related
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.
I've recently came across the following situation and looking for an advice / elegant solution of pretty tricky misbehave, IMHO.
Let's assume that I've created the following custom element definition in file bible-reference.js:
class BibleReference extends HTMLElement {
constructor() {
super();
}
get value() { return this._value; }
set value(newValue) { this._value = newValue; }
}
customElements.define('bible-reference', BibleReference);
We've defined our new element, and we have a value property getter/setter to interact with this element's value. Pay attention, that the getter/setter will be found on the element's prototype object, which is okay in itself, to be sure.
Now, let's review the following HTML layout that happened to be in my case:
<bible-reference id='elem-1'></bible-reference>
<script src="starter.js"></script>
<script src="bible-reference.js"></bible-reference>
The trick is in that starter.js content. If this script contains code below, things get broken:
let br = document.getElementById('elem-1');
br.value = 'some value';
The value is set on the non-upgraded yet element, therefore it is not the getter/setter that are being invoked, but just a new property is added to the element. Having this property set on the object itself, even after the upgrade is done the value's getter/setter are not accessible for this element instance anymore, since there is an (overriding) value property available before getting to the prototype.
I've solved it meanwhile by adding delete this.value; to the constructor, but... what a hack!
Any insights appreciated.
Another way to handle the issue would be to define the value property in the closure of the constructor().
<bible-reference id='elem-1'></bible-reference>
<script>
let br = document.getElementById('elem-1');
br.value = 'Genesis 1:1';
class BibleReference extends HTMLElement {
constructor() {
super()
var _value = this.value
Reflect.defineProperty(this, 'value', {
get: () => _value,
set: newValue => _value = newValue
})
}
}
customElements.define('bible-reference', BibleReference);
console.log(br.value)
</script>
But I guess you'll say it's a bad hack ;-) Anyway I'd prefer your delete solution.
Hello I am wondering how to write a proper wrapper for something like sessionStorage.
I could implement a class of my own and proxy through method invocation to the sessionStorage. For example with some quick pseudocode:
class customStorage {
set(key, value) {
sessionStorage.setItem(key, value);
}
}
I would use it like so:
const customStorage = new customStorage();
customStorage.set('my-key', 'hello there');
All fine and dandy but I would like for a user to have the freedom to use other native sessionStorage methods on my proxy that I might not implement myself in my proxy.
for something like sessionStorage it would be do-able to write them all yourself even though all they might do is proxy through to sessionStorage without any intervention.
For something larger where I would only manipulate 5 methods out of 20 or something this does not seem viable.
Overwriting native functionality with prototype also seems like a deathtrap leading to many wtfs-per-minute.
So far what I have read from 'proxy patterns' in Javascript they all implemented all of the methods from the original Object. Am I forced to do this?
Is there some sort of way to create an ES6 class and set the prototype of that very class to sessionStorage in the constructor or something?
I would like for a user to have the freedom to use other native sessionStorage methods on my proxy that I might not implement myself in my proxy.
I would rather give the user the freedom to just use the native sessionStorage directly if he intends to do that. Your implementation does have its own, separate functionality, which does use sessionStorage internally but is not the sessionStorage. There's no reason to implement its interface on your object. (See also composition over inheritance).
Is there some sort of way to create an ES6 class and set the prototype of that very class to sessionStorage in the constructor or something?
No. Even when you want to implement that interface, your objects are not really SessionStorage instances. Also in this particular case, sessionStorage is a singleton and you cannot instantiate a second SessionStorage, so inheritance does absolutely not work here.
There are three ways around this (I'll write code for the generic case with instantiation from arbitrary objects to be wrapped, you likely want a static singleton-like one for your custom storage):
Mixins to decorate the object. Don't create another instance, just overwrite the properties on the original. (This is likely out of the question for builtin objects)
function custom(orig) {
orig.get = function() { … };
return orig;
}
Parasitical inheritance, creating a complete wrapper using reflection on the object.
function custom(orig) {
const obj = {
get() { … };
};
for (const p in orig) { // assuming everything is enumerable - alternatively use
// for (const p of Object.getOwnPropertyNames(…))
// or even incorporating the prototype chain
obj[p] = typeof orig[p] == "function"
? (...args) => orig[p](...args)
: orig[p];
}
return obj;
}
A literal Proxy with a suitable handler:
const customMethods = {
get() { … }
};
const handler = {
has(target, name) {
return name in customMethods || name in target;
},
get(target, name) {
if (name in customMethods) return customMethods[name];
else return target[name];
// if its a native object with methods that rely on `this`, you'll need to
// return target[name].bind(target)
// for function properties
}
}
function custom(orig) {
return new Proxy(orig, handler);
}
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
}
In Typescript, how would I use a getter/setter for all the properties of an object? For example, I can have the following code:
class Div {
private _container: HTMLDivElement;
public get container() {
return this._container;
}
public set container(value) {
alert("Updated!");
this._container = value;
}
testDiv() {
this.container = <HTMLDivElement>document.createElement('div');
this.container.style.width = "100px";
}
}
var newDiv: Div = new Div();
newDiv.testDiv();
"Updated!" is alerted only once - when the div is first set (which, I guess, is really what it should be), but I want it to alert "Updated!" even when I'm setting a property on that object. Is there a way to do this, or should I come up with some kind of workaround?
Create an event system that triggers when your objects state changes. Do not allow direct access to properties. Funnel all activity through a controller that triggers state change as necessary.
Or look into using a framework that already does this. Backbone, Knockout, Angularjs etc.