最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

mixins - Javascript: Mixing in a getter (object spread) - Stack Overflow

programmeradmin0浏览0评论

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?

Share Improve this question edited Dec 25, 2017 at 17:35 Felix Kling 817k181 gold badges1.1k silver badges1.2k bronze badges asked Dec 23, 2017 at 11:54 tifreltifrel 4511 gold badge8 silver badges22 bronze badges 5
  • 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
Add a ment  | 

1 Answer 1

Reset to default 9

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 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.

发布评论

评论列表(0)

  1. 暂无评论