Javascript: Mixing in a getter (object spread)

一笑奈何 提交于 2020-01-04 14:11:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!