I am refactoring some code from an older ES5 codebase, where I was doing the following:
function ObjectCreatorFunction() {
this.someProperty= {};
}
/*
* Static method, to extend the object passed as a parameter with the
* ObjectCreatorFunction instance properties and methods
*/
ObjectCreatorFunction.extend = function extend(object) {
var key;
ObjectCreatorFunction.call(object);
for (key in ObjectCreatorFunction.prototype) {
if (ObjectCreatorFunction.prototype.hasOwnProperty(key)) {
if (!object.hasOwnProperty(key)) {
object[key] = ObjectCreatorFunction.prototype[key];
}
}
}
return object;
};
ObjectCreatorFunction.prototype.someMethod = function someMethod() {...}
//etc
I am trying to do the same with an ES6 rewrite, so I a have this
class ClassName{
constructor() {
this.someProperty= {};
}
static extend(object) {
let key;
ClassName.constructor.call(object);
for (key in ClassName.prototype) {
if (ClassName.prototype.hasOwnProperty(key)) {
if (!object.hasOwnProperty(key)) {
object[key] = ClassName.prototype[key];
}
}
}
return object;
}
someMethod() {...}
//etc
}
My problem is that the line
ClassName.constructor.call(object);
does not work as intended, i.e. The passed object does not get the instance properties of the class.
I have tried s few ways to rewrite this (even some unorthodox ones) to no avail.
How do I extend an object with a class' instance properties, using ES6?
DISCLAIMER:
My code is passed through a transpiling process, with babel and webpack. In case it has any effect to how classes work internally.
No, this does not work with class syntax. It's a bit more than just syntactic sugar. The prototypical inheritance stayed the same, but the initialisation of instances works differently now especially for inherited classes, and you cannot invoke a constructor without new to not create a new instance.
I would recommend to be explicit about your mixin, and give it an init method:
class Mixin {
constructor(methods) {
this.descriptors = Object.getOwnPropertyDescriptors(methods);
}
extend(object) {
for (const p in this.descriptors)) {
if (Object.prototype.hasOwnProperty.call(object, p)) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`Object already has property "${p}"`);
}
} else {
Object.defineProperty(object, p, this.descriptors[p]);
}
}
}
}
// define a mixin:
const xy = new Mixin({
initXY() {
this.someProperty= {};
},
someMethod() { … }
});
// and use it:
class ClassName {
constructor() {
this.initXY();
}
}
xy.extend(ClassName.prototype);
Related
I'm using an entity-component system. I've defined some components as ES6 classes, and I can create instances of those components by calling their constructors with new. I'm trying to use these class instances as mixins. I've found that using Object.assign doesn't copy the methods of those instances onto my target object because the methods are bound to the object's prototype.
I've found a hacky solution as follows:
function getAllPropertyNames(obj) {
return Object
.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertyNames(obj.__proto__))
.filter(name => (name !== "constructor"))
}
function assembleFromComponents(obj, ...cmp) {
for(let i of cmp) {
for(let j of getAllPropertyNames(i)) {
obj[j] = i[j]
}
}
}
This method is not ideal because it doesn't access the component's full prototype chain, although I don't think I'll need it anyway. However, upon inspection, getters and setters don't appear to work.
Is there a better way to use a class instance as a mixin?
I probably wouldn't define mixins using class syntax. I'd define them as objects:
const myMixin = {
doThis() {
// ...
},
doThat() {
// ...
},
// ...
};
One issue with class syntax is that super may not work as you expect, because even after being copied, the methods will still refer to their original home object. (Or that may be what you expect in which case you're fine.) More on this below.
But if you want to use class syntax, you can define an Object.assign-like function that applies all of the methods and other properties from the entire chain via Object.defineProperties and Object.getOwnPropertyDescriptors, which will copy getters and setters. Something like (off the cuff, not tested):
function assignAll(target, source, inherited = false) {
// Start from the prototype and work upward, so that overrides work
let chain;
if (inherited) {
// Find the first prototype after `Object.prototype`
chain = [];
let p = source;
do {
chain.unshift(p);
p = Object.getPrototypeOf(p);
} while (p && p !== Object.prototype);
} else {
chain = [source];
}
for (const obj of chain) {
// Get the descriptors from this object
const descriptors = Object.getOwnPropertyDescriptors(obj);
// We don't want to copy the constructor or __proto__ properties
delete descriptors.constructor;
delete descriptors.__proto__;
// Apply them to the target
Object.defineProperties(target, descriptors);
}
return target;
}
Using it:
assignAll(Example.prototype, Mixin.prototype);
Live Example:
function assignAll(target, source, inherited = false) {
// Start from the prototype and work upward, so that overrides work
let chain;
if (inherited) {
// Find the first prototype after `Object.prototype`
chain = [];
let p = source;
do {
chain.unshift(p);
p = Object.getPrototypeOf(p);
} while (p && p !== Object.prototype);
} else {
chain = [source];
}
for (const obj of chain) {
// Get the descriptors from this object
const descriptors = Object.getOwnPropertyDescriptors(obj);
// We don't want to copy the constructor or __proto__ properties
delete descriptors.constructor;
delete descriptors.__proto__;
// Apply them to the target
Object.defineProperties(target, descriptors);
}
return target;
}
class Example {
method() {
console.log("this is method");
}
}
const mixinFoos = new WeakMap();
class Mixin {
mixinMethod() {
console.log("mixin method");
}
get foo() {
let value = mixinFoos.get(this);
if (value !== undefined) {
value = String(value).toUpperCase();
}
return value;
}
set foo(value) {
return mixinFoos.set(this, value);
}
}
assignAll(Example.prototype, Mixin.prototype, true);
const e = new Example();
e.foo = "hi";
console.log(e.foo);
// HI
Here's an example where the mixin is a subclass and uses super, just to demonstrate what super means in that context:
function assignAll(target, source, inherited = false) {
// Start from the prototype and work upward, so that overrides work
let chain;
if (inherited) {
// Find the first prototype after `Object.prototype`
chain = [];
let p = source;
do {
chain.unshift(p);
p = Object.getPrototypeOf(p);
} while (p && p !== Object.prototype);
} else {
chain = [source];
}
for (const obj of chain) {
// Get the descriptors from this object
const descriptors = Object.getOwnPropertyDescriptors(obj);
// We don't want to copy the constructor or __proto__ properties
delete descriptors.constructor;
delete descriptors.__proto__;
// Apply them to the target
Object.defineProperties(target, descriptors);
}
return target;
}
class Example {
method() {
console.log("this is Example.method");
}
}
class MixinBase {
method() {
console.log("this is MixinBase.method");
}
}
class Mixin extends MixinBase {
method() {
super.method();
console.log("this is Mixin.method");
}
}
assignAll(Example.prototype, Mixin.prototype, true);
const e = new Example();
e.method();
// "this is MixinBase.method"
// "this is Mixin.method"
You've said you want to use class instances as mixins. The above works just fine doing that. Here's an example:
function assignAll(target, source, inherited = false) {
// Start from the prototype and work upward, so that overrides work
let chain;
if (inherited) {
// Find the first prototype after `Object.prototype`
chain = [];
let p = source;
do {
chain.unshift(p);
p = Object.getPrototypeOf(p);
} while (p && p !== Object.prototype);
} else {
chain = [source];
}
for (const obj of chain) {
// Get the descriptors from this object
const descriptors = Object.getOwnPropertyDescriptors(obj);
// We don't want to copy the constructor or __proto__ properties
delete descriptors.constructor;
delete descriptors.__proto__;
// Apply them to the target
Object.defineProperties(target, descriptors);
}
return target;
}
class Example {
method() {
console.log("this is Example.method");
}
}
class MixinBase {
method() {
console.log("this is MixinBase.method");
}
}
const mixinFoos = new WeakMap();
class Mixin extends MixinBase {
constructor(value) {
super();
this.value = value;
}
mixinMethod() {
console.log(`mixin method, value = ${this.value}`);
}
get foo() {
let value = mixinFoos.get(this);
if (value !== undefined) {
value = String(value).toUpperCase();
}
return value;
}
set foo(value) {
return mixinFoos.set(this, value);
}
method() {
super.method();
console.log("this is Mixin.method");
}
}
// Here I'm using it on `Example.prototype`, but it could be on an
// `Example` instance as well
assignAll(Example.prototype, new Mixin(42), true);
const e = new Example();
e.mixinMethod();
// "mixin method, value = 42"
e.method();
// "this is MixinBase.method"
// "this is Mixin.method"
e.foo = "hi";
console.log(e.foo);
// "HI"
But really, you can design it however you want to; assignAll is just an example, as are the runnable ones above. The key things here are:
Use Object.getOwnPropertyDescriptors to get property descriptors and Object.defineProperties (or their singular counterparts, getOwnPropertyDescriptor and defineProperty), so that accessor methods get transferred as accessors.
Work from the base prototype up to the instance, so that overriding at each level works correctly.
super will continue to work in its original inheritance chain, not in the new location the mixin has been copied to.
I understand that Proxy can be used to alter object-level behaviors, such as bracket notation get and set. All the examples I can find show constructing an object and then wrapping it with a Proxy call. Is there a way to define a class Foo, using ES6 class constructor notation, such that the returned object from the constructor is already wrapped in Proxy, rather than the caller of the constructor having to also call Proxy separately?
Thanks in advance.
If I understand your question properly, what you want to do, is in the constructor return a new proxy like this:
class MyClass {
constructor() {
return new Proxy(this, {
// Proxy settings here
})
}
}
Here in this example, we create a new class, then call a few properties. The proxy will then just print out the properties that were called for simplicity.
class MyClass {
constructor() {
return new Proxy(this, {
get: (target, key) => {
console.log('I am the key: ' + key)
return Reflect.get(target, key)
}
})
}
}
let c = new MyClass
c.awesome
c.billy
c.superTroopers
if (c instanceof MyClass) {
console.log('I am an instance of MyClass')
} else {
console.log('I am not an instance of MyClass')
}
As far as I know: no, but you can set the prototype afterwards. Something like this:
class Thing {
constructor() {
// ...
}
}
Thing.prototype = new Proxy(Thing.prototype, {
get(target, name) {
// ...
}
});
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
I have a Javascript class (in ES6) that is getting quite long. To organize it better I'd like to split it over 2 or 3 different files. How can I do that?
Currently it looks like this in a single file:
class foo extends bar {
constructor(a, b) {} // Put in file 1
methodA(a, b) {} // Put in file 1
methodB(a, b) {} // Put in file 2
methodC(a, b) {} // Put in file 2
}
Thanks!
When you create a class
class Foo extends Bar {
constructor(a, b) {
}
}
you can later add methods to this class by assigning to its prototype:
// methodA(a, b) in class Foo
Foo.prototype.methodA = function(a, b) {
// do whatever...
}
You can also add static methods similarly by assigning directly to the class:
// static staticMethod(a, b) in class Foo
Foo.staticMethod = function(a, b) {
// do whatever...
}
You can put these functions in different files, as long as they run after the class has been declared.
However, the constructor must always be part of the class declaration (you cannot move that to another file). Also, you need to make sure that the files where the class methods are defined are run before they are used.
Here's my solution. It:
uses regular modern classes and .bind()ing, no prototype. (EDIT: Actually, see the comments for more on this, it may not be desirable.)
works with modules. (I'll show an alternative option if you don't use modules.)
supports easy conversion from existing code.
yields no concern for function order (if you do it right).
yields easy to read code.
is low maintenance.
unfortunately does not play well with static functions in the same class, you'll need to split those off.
First, place this in a globals file or as the first <script> tag etc.:
BindToClass(functionsObject, thisClass) {
for (let [ functionKey, functionValue ] of Object.entries(functionsObject)) {
thisClass[functionKey] = functionValue.bind(thisClass);
}
}
This loops through an object and assigns and binds each function, in that object, by its name, to the class. It .bind()'s it for the this context, so it's like it was in the class to begin with.
Then extract your function(s) from your class into a separate file like:
//Use this if you're using NodeJS/Webpack. If you're using regular modules,
//use `export` or `export default` instead of `module.exports`.
//If you're not using modules at all, you'll need to map this to some global
//variable or singleton class/object.
module.exports = {
myFunction: function() {
//...
},
myOtherFunction: function() {
//...
}
};
Finally, require the separate file and call BindToClass like this in the constructor() {} function of the class, before any other code that might rely upon these split off functions:
//If not using modules, use your global variable or singleton class/object instead.
let splitFunctions = require('./SplitFunctions');
class MySplitClass {
constructor() {
BindToClass(splitFunctions, this);
}
}
Then the rest of your code remains the same as it would if those functions were in the class to begin with:
let msc = new MySplitClass();
msc.myFunction();
msc.myOtherFunction();
Likewise, since nothing happens until the functions are actually called, as long as BindToClass() is called first, there's no need to worry about function order. Each function, inside and outside of the class file, can still access any property or function within the class, as usual.
I choose to have all privte variables/functions in an object called private, and pass it as the first argument to the external functions.
this way they have access to the local variables/functions.
note that they have implicit access to 'this' as well
file: person.js
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
// delegate getAge to PersonGetAge in an external file
// pass this,privates,args
this.getAge=function(...args) {
return PersonGetAge.apply(this,[privates].concat(args));
}
// delegate setAge to PersonSetAge in an external file
// pass this,privates,args
this.setAge=function(...args) {
return PersonSetAge.apply(this,[privates].concat(args));
}
}
file: person_age_functions.js
exports.PersonGetAge =function(privates)
{
// note: can use 'this' if requires
return privates.age;
}
exports.PersonSetAge =function(privates,age)
{
// note: can use 'this' if requires
privates.age=age;
}
file: main.js
const { Person } = require('./person.js');
let me = new Person();
me.setAge(17);
console.log(`I'm ${me.getAge()} years old`);
output:
I'm 17 years old
note that in order not to duplicate code on person.js, one can assign all functions in a loop.
e.g.
person.js option 2
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
{
// assign all external functions
let funcMappings={
getAge:PersonGetAge,
setAge:PersonSetAge
};
for (const local of Object.keys(funcMappings))
{
this[local]=function(...args) {
return funcMappings[local].apply(this,[privates].concat(args));
}
}
}
}
You can add mixins to YourClass like this:
class YourClass {
ownProp = 'prop'
}
class Extension {
extendedMethod() {
return `extended ${this.ownProp}`
}
}
addMixins(YourClass, Extension /*, Extension2, Extension3 */)
console.log('Extended method:', (new YourClass()).extendedMethod())
function addMixins() {
var cls, mixin, arg
cls = arguments[0].prototype
for(arg = 1; arg < arguments.length; ++ arg) {
mixin = arguments[arg].prototype
Object.getOwnPropertyNames(mixin).forEach(prop => {
if (prop == 'constructor') return
if (Object.getOwnPropertyNames(cls).includes(prop))
throw(`Class ${cls.constructor.name} already has field ${prop}, can't mixin ${mixin.constructor.name}`)
cls[prop] = mixin[prop]
})
}
}
TypeScript Solution
foo-methods.ts
import { MyClass } from './class.js'
export function foo(this: MyClass) {
return 'foo'
}
bar-methods.ts
import { MyClass } from './class.js'
export function bar(this: MyClass) {
return 'bar'
}
class.ts
import * as barMethods from './bar-methods.js'
import * as fooMethods from './foo-methods.js'
const myClassMethods = { ...barMethods, ...fooMethods }
class _MyClass {
baz: string
constructor(baz: string) {
this.baz = baz
Object.assign(this, myClassMethods);
}
}
export type MyClass = InstanceType<typeof _MyClass> &
typeof myClassMethods;
export const MyClass = _MyClass as unknown as {
new (
...args: ConstructorParameters<typeof _MyClass>
): MyClass;
};
My solution is similar to the one by Erez (declare methods in files and then assign methods to this in the constructor), but
it uses class syntax instead of declaring constructor as a function
no option for truly private fields - but this was not a concern for this question anyway
it does not have the layer with the .apply() call - functions are inserted into the instance directly
one method per file: this is what works for me, but the solution can be modified
results in more concise class declaration
1. Assign methods in constructor
C.js
class C {
constructor() {
this.x = 1;
this.addToX = require('./addToX');
this.incX = require('./incX');
}
}
addToX.js
function addToX(val) {
this.x += val;
return this.x;
}
module.exports = addToX;
incX.js
function incX() {
return this.addToX(1);
}
module.exports = incX;
2. Same, but with instance fields syntax
Note that this syntax is a Stage 3 proposal as of now.
But it works in Node.js 14 - the platform I care about.
C.js
class C {
x = 1;
addToX = require('./addToX');
incX = require('./incX');
}
Test
const c = new C();
console.log('c.incX()', c.incX());
console.log('c.incX()', c.incX());
It seems possible to nest a class in a constructor which can then be instantiated from anywhere within the class, is this official?
[EDIT] E.g.,
class C {
constructor() {
class D {
constructor() { }
}
}
method() {
var a = new D(); // works fine
}
}
//var a = new D(); // fails in outer scope
The traceur generated JS https://google.github.io/traceur-compiler/demo/repl.html
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
var C = function C() {
var D = function D() {};
($traceurRuntime.createClass)(D, {}, {});
};
($traceurRuntime.createClass)(C, {method: function() {
var a = new D();
}}, {});
return {};
});
//# sourceURL=traceured.js
No, there are no nested class scopes in ES6, and there is no such thing as private members in the class syntax anyway if you mean that.
Of course you can put a second class as a static property on another class, like this:
class A {
…
}
A.B = class {
…
};
or you use an extra scope:
var C;
{
class D {
constructor() { }
}
C = class C {
constructor() { }
method() {
var a = new D(); // works fine
}
}
}
(There seems to be a bug with traceur as it uses a hoisted var for the class declaration instead of block scope)
With the class field syntax, it will also be possible to write a single expression or declaration:
class A {
…
static B = class {
…
}
};
something like that?
class A {
constructor () {
this.B = class {
echo () {
console.log('I am B class');
}
}
}
echo () {
this.b = new this.B;
this.b.echo();
}
}
var a = new A;
a.echo();
You could use a getter:
class Huffman {
constructor() { /* ... */ }
static get Node() {
return class Node {
constructor() {
var API = this;
API.symbol = 0; API.weight = 0;
return API;
}
};
}
get Node() {
return Huffman.Node;
}
encode() { /* ... */ }
decode() { /* ... */ }
/* ... */
}
// usage
huffman = new Huffman;
new huffman.Node;
new Huffman.Node;
Which in latest Chrome Dev 44.0.2376.0 on Apple 10.10.2 gives in console
new huffman.Node
Node {symbol: 0, weight: 0}
new Huffman.Node
Node {symbol: 0, weight: 0}
In other news, getters are the secret sauce that let's you do a whole bunch of cool things in ES6.
Please Note The above construction breaks instanceof for Node (why? because a whole new class is defined with every get call). To not break instanceof define Node outside of the scope of a single getter, either in the constructor (disabling the Huffman.Node class property and causing instanceof to work within the namespace of a single Huffman instance, and break outside that), or define Node in a sibling or ancestor scope to Huffman (allowing instanceof to work in all scopes below that the one where Node is defined).
When you create a nested child class in a constructor of a parent class, this means every instance of the parent class has its own child class. Typically this is not what you want. Instead you want a child class, which is shared among all instances of the parent class. This means the nested class must be static. This is an example:
class Parent
{
static Child = class Child {
constructor (name) { console.log (`Child: ${name}`); }
}
constructor (...names) {
console.log ('Parent');
this.children = names.map (name => new Parent.Child (name));
}
}
var p = new Parent ('Alice', 'Bob');
console.log (`same type? ${p.children[0].constructor === p.children[1].constructor}`);