Search code examples
javascriptecmascript-6ecmascript-5es6-class

How to extend object with ES6 class instance properties and methods


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.


Solution

  • 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);