问题
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 compared 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:
This 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 comma 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.
来源:https://stackoverflow.com/questions/47952284/javascript-mixing-in-a-getter-object-spread