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
}
Related
I'm in the process of converting a JavaScript project to Typescript.
I have an old module file that was require()-d from the main.js file of the project.
Here is the simplified example:
//required_module.js
const foo = require("bar");
module.exports = {
start(myvar) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type) {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type) {
return this.myvar.asd(type);
}
};
As you can see, this is just a simple JS module file (not a class), that uses this many times, and it just works as expected.
However as I try to convert this module to a TypeScript module without creating a class from it, I get different kind of errors at the this references as I try different approaches, like:
Object is possibly 'undefined'.ts(2532)
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
An outer value of 'this' is shadowed by this container.
Is the only solution to create a class or TypeScript also allows using this outside of classes in a specific way?
Based on the helpful comments, the answer is:
You probably don't have to convert it to a class, but you'd be better off: you are going to run into issues dynamically adding properties (like myvar) or calling methods on untyped objects. Typescript doesn't want you treating JS objects as a random grab-bag of properties: there's already another data structure for that- Jared Smith
I'd focus less on converting to typescript and more on converting to modern module syntax. If you used named exports instead of object methods, you wouldn't have these issues. You would however have a stateful, static, module-scoped myvar variable (as you currently do), which should be avoided. Instead of the singleton, a class that can be instantiated multiple times might be the better approach.- Alex Wayne
The example, simply converted code would look like something like this in TypeScript:
import foo from 'bar';
export default {
myvar: 0,
start(myvar: number) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type: 'str1' | 'str2') {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type: string) {
//...
}
};
I have a class I reuse, often, to the tune of possibly tens of thousands of instances in a given session. It occurred to me that creating all these properties within the constructor may be replication, that is each function is unique in memory and not a pointer, so I created a little test setup:
const testTree = function (){
console.log(this, this.konnichiwa);
}
const testFjord = function (aloha){
return function() {
console.log(this, aloha, this.konnichiwa);
}
}
class Clown extends Object{
constructor(props){
super(props);
const aloha = "Hello!"; //<- Private party
this.konnichiwa = "Also hello I think"; //<- Everyone's invited
this.testWan = () => {
console.log(this, aloha, this.konnichiwa);
}
this.testTree = testTree;
this.testFjord = testFjord(aloha);
}
testToo = () => {
console.log(this, this.konnichiwa);
}
}
//export default Clown; //this is an export in my application, used lots
const test = new Clown();
const otherTest = new Clown();
console.log(test.testWan === otherTest.testWan);
console.log(test.testToo === otherTest.testToo);
console.log(test.testTree === otherTest.testTree);
console.log(test.testFjord === otherTest.testFjord);
test.testWan();
test.testToo();
test.testTree();
test.testFjord();
Part 1
As you can test above, testWan, testToo, and testFjord are all unique per instance, but testTree is not. Is there any way to declare a "pointer"/"reusable function" but inside class constructor?
The issue here with testToo and testTree is that they can't access private vars within the constructor like testWan can. testFjord is a factory and can be passed these, but then the returned function is unique and won't be able to interact well with vars passed into it.
It's very likely not possible - I think it's a catch 22 scope thing - but you may know better. The only recourse I can think of is to add a property to this for each thing I need to use in testTree, but that exposes things I may not want exposed outside of the class.
Part 2
This part only applies if this is a generally consistent behavior, and not something completely unique per-browser. Does the engine hold onto references to things like conditionals (which I suspect are sorta anonymous-function-like behind the scenes) once the constructor has run?
I have a fairly knarly conditional setup I'm not going to shove in the code here. This is entirely within the constructor right now. I suspect that, although not a function declaration itself, it is also not a pointer, but an entirely fresh instance per the 'new' in new Clown. It needs to manipulate some private vars and so per Part 1 I haven't figured out a good way to extract this.
Example, there are references to private vars inside the constructor for exposed functions: aloha above is private but used by public testWan function, and so needs to be held after constructor has executed. Is the entire constructor held for the life of test & otherTest or is the constructor going to be dropped after use and just the reference to aloha held in memory?
I want a JavaScript class that can conditionally add additional methods into itself from separate files. The idea is to separate different concerns of the app into more manageable self-contained modules that nevertheless can interact with the methods in the mother app class. Therefore, the additional methods in the separate file must be able to reference the methods and variables in the main class. See the code sample below.
I looked at a lot of different solutions, but all of them have a downside for what I'm wanting.
I could do new Uploads(), but I could not find a way for methods in Uploads.js to reference methods in the main App class
I could use extends, but this would not allow for conditional extension AFAIK
I could just define new methods into the prototype itself, but this would mean that the external file would need to "know" about the class it's going to be used in, which doesn't make it widely reusable.
The best I have so far is the following:
app.js
const Uploads = require("./Uploads.js");
const config = { hasUploads: true }; // Probably loaded from a file
class App {
constructor() {
/* Only include the separate module if the config says so */
if(config.hasUploads) {
Object.assign(this, Uploads);
}
}
foo() {
/* Something */
}
}
Uploads.js
module.exports = {
bar() {
this.foo();
}
};
It works, but I don't know if this is the best solution;
There's no constructor, so if Uploads.js needs to do some setup, app.js needs to contain the logic to do so (or at least know to call some uniquely named faux constructor method), which doesn't seem ideal.
If Uploads.js contains a method with the same name as app.js or any other possible module being loaded, they will be overwritten, leading into unexpected behaviour.
The Uploads.js is an object of functions, whereas app.js defines a class. Ideally (though I guess not necessarily) for code manageability they should both use the same syntax.
Is there a better/cleaner/nicer way of doing this?
Instead of trying to perform some kind of crazy multi inheritance, why not try embracing composition? Its very good for solving these kinds of problems.
class App {
constructor(modules) {
if (modules.uploads) {
this.uploads = modules.uploads(this);
}
}
foo() {
console.log('foo!');
}
}
class Uploads {
constructor(context) {
this.context = context;
}
method() {
this.context.foo();
}
}
const app = new App({ uploads: (ctx) => new Uploads(ctx) });
app.uploads.method();
You can get really fancy with this and use builders to configure apps with specific types of modules.
Depending on your anticipated complexity, you might want to think about using event buses, mediators, or commands to decouple things from the host itself.
One option to fix overwriting an existing method from the uploads file is to assign new methods in a loop and check for duplicates (Object.assign is not ideal in this case) and only add updates once:
const Uploads = {
bar() {
this.foo("called from bar");
}
};
const config = { hasUploads: true, // Probably loaded from a file
configured: false
};
class App {
constructor() {
/* Only include the separate module if the config says so */
if(config.hasUploads && !config.configured) {
const proto = this.constructor.prototype;
const methods = Object.keys(Uploads);
methods.forEach( name=> {
if( proto[ name] ) {
throw new Error( "App already has method " + name);
}
proto[name] = Uploads[name];
});
config.configured = true;
}
}
foo(arg) {
/* Something */
console.log( arg );
}
}
const app = new App();
app.bar();
A better (cleaner) alternative might be to add updates to the class before calling its constructor, using a static class method because its this value is the constructor function. Tested example:
static addMethods(uploads) { // inside class declaration
const proto = this.prototype;
for (const [name, method] of Object.entries(uploads)) {
if( proto[name]) {
throw new Error("App already has a ${name} method");
}
proto[name] = method;
}
}
to be called as needed by
if( config.hasUploads) {
App.addMethods( Uploads);
}
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";
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);
}