I tried creating mixing in a getter into a JS object via the spread operator syntax, however it always seems to return null
.
HTML:
<body>
<div id="wrapperA"></div>
<div id="wrapperB"></div>
</body>
<script src='./test.js'></script>
JS:
"use strict";
const mixin = {
get wrapper() { return document.getElementById(this.wrappername); }
}
const wrapperA = {
wrappername: 'wrapperA',
...mixin
}
const wrapperB = {
wrappername: 'wrapperB',
...mixin
}
console.log(wrapperA);
console.log(wrapperB);
Console output:
{wrappername: "wrapperA", wrapper: null}
{wrappername: "wrapperB", wrapper: null}
This links to an extension function that is supposed to work, and from what I can tell the code above created an unintentional closure. However, it reads quite poorly pared to the ...
syntax. Does anybody know how to get the code to work with the latter solution? Do the ES devs know about this issue and will it be fixed in ES7?
I tried creating mixing in a getter into a JS object via the spread operator syntax, however it always seems to return null
.
HTML:
<body>
<div id="wrapperA"></div>
<div id="wrapperB"></div>
</body>
<script src='./test.js'></script>
JS:
"use strict";
const mixin = {
get wrapper() { return document.getElementById(this.wrappername); }
}
const wrapperA = {
wrappername: 'wrapperA',
...mixin
}
const wrapperB = {
wrappername: 'wrapperB',
...mixin
}
console.log(wrapperA);
console.log(wrapperB);
Console output:
{wrappername: "wrapperA", wrapper: null}
{wrappername: "wrapperB", wrapper: null}
This links to an extension function that is supposed to work, and from what I can tell the code above created an unintentional closure. However, it reads quite poorly pared to the ...
syntax. Does anybody know how to get the code to work with the latter solution? Do the ES devs know about this issue and will it be fixed in ES7?
-
1
Why not simply:
class Wrapper { constructor(wrapperId) { this.id = wrapperId; } get wrapper() { return document.getElementById(this.id); }}
? – Andreas Commented Dec 23, 2017 at 12:03 - 1. I tend to avoid classes altogether, which is the reason I did not use them in that particular case. – tifrel Commented Dec 23, 2017 at 12:14
- 2. (which is the reason for 1.) multiple inheritance. Again I would have the possibility of using a prototype chain, but I prefer mixins to a huge extent for a number of reasons. – tifrel Commented Dec 23, 2017 at 12:22
- A script element after the closing body tag is invalid HTML, so the script shouldn't be executed. However, browsers probably correct the invalid HTML and put the script before the closing tag. – RobG Commented Dec 23, 2017 at 13:57
- 1 "ES7" was ECMAScript 2016, it's already superseded by the current version, ECMAScript 2017 (ed 8). Spread properties in object initialisers might be in ECMAScript 2018 (ed 9). ;-) – RobG Commented Dec 23, 2017 at 14:11
1 Answer
Reset to default 9This is not a bug. When the spread syntax is interpreted, the property values of mixin
are each evaluated, i.e. the wrapper
getter is called with this
set to mixin
. Note that this
is not the new object that is being constructed, as ...
has precedence over the ma sequencing. So at the moment the ...
is executed, the final object is not in view. Secondly, the copied property is no longer a getter, but a plain property with an atomic value (not a function).
The behaviour can maybe be better understood with the almost identical process that executes when you use Object.assign
:
Object.assign({
wrappername: 'wrapperA'
}, mixin);
If you want the wrapper
getter to be called with the new object as this
, then do:
"use strict";
class Wrapper {
constructor(wrappername) {
this.wrappername = wrappername;
}
get wrapper() {
return document.getElementById(this.wrappername);
}
}
const wrapperA = new Wrapper('wrapperA');
const wrapperB = new Wrapper('wrapperB');
console.log(wrapperA.wrapper);
console.log(wrapperB.wrapper);
<div id="wrapperA"></div>
<div id="wrapperB"></div>
Multiple Inheritance
If you really need multiple inheritance, then look at a library such as Ring.js, which makes this really easy.
There are several Q&A on mixin implementations on StackOverflow. Here is one of the many ideas, derived from this article:
"use strict";
function MixinNameGetter(superclass) {
return class extends superclass {
get wrapper() {
return document.getElementById(this.wrappername);
}
}
}
function MixinLetterGetter(superclass) {
return class extends superclass {
get letter() {
return this.wrappername.substr(-1);
}
}
}
class Wrapper {
constructor(wrappername) {
this.wrappername = wrappername;
}
}
class ExtendedWrapper extends MixinNameGetter(MixinLetterGetter(Wrapper)) {
}
const wrapperA = new ExtendedWrapper('wrapperA');
const wrapperB = new ExtendedWrapper('wrapperB');
console.log(wrapperA.wrapper, wrapperA.letter);
console.log(wrapperB.wrapper, wrapperB.letter);
<div id="wrapperA"></div>
<div id="wrapperB"></div>
Although this effectively provides multiple inheritance, the resulting hierarchy of classes derived from expressions is not really an ingredient for efficient code.
Decorators
Another approach is to abandon the idea of mixins and use decorators instead:
"use strict";
function DecoratorNameGetter(target) {
Object.defineProperty(target, 'wrapper', {
get: function () {
return document.getElementById(this.wrappername);
}
});
}
function DecoratorLetterGetter(target) {
Object.defineProperty(target, 'letter', {
get: function () {
return this.wrappername.substr(-1);
}
});
}
class Wrapper {
constructor(wrappername) {
this.wrappername = wrappername;
DecoratorNameGetter(this);
DecoratorLetterGetter(this);
}
}
const wrapperA = new Wrapper('wrapperA');
const wrapperB = new Wrapper('wrapperB');
console.log(wrapperA.wrapper, wrapperA.letter);
console.log(wrapperB.wrapper, wrapperB.letter);
<div id="wrapperA"></div>
<div id="wrapperB"></div>
This leads to a flat structure (no prototype chain) where the extension happens in the target object itself.