cf.completefrontend

Write the codeHard

Write mixin(Target, ...sources) that adds methods, getters, and setters from plain objects to a class.

Before you start

What's a mixin?

A class can extend only one parent class. But sometimes classes that aren't related need the same few methods, such as a fullName getter for a Customer and a Speaker that already extend different parents. In its simplest form, a mixin is a plain object that holds those shared methods, getters, and setters. Instead of being inherited, its properties are added to each class's prototype, the object that all the class's instances get their methods from, so every instance can use them as if the class had defined them itself.

Inheritance says what something is; a mixin adds something it can do. A Speaker is an EventTarget, and with a mixin it can also give its full name.

// A mixin: a plain object with a getter and a method to share
const HasFullName = {
  get fullName() {
    return `${this.first} ${this.last}`;
  },
  initials() {
    return this.first[0] + this.last[0];
  },
};
 
// A class that already has a parent, so it can't extend a class with these too
class Speaker extends EventTarget {
  constructor(first, last) {
    super();
    this.first = first;
    this.last = last;
  }
}

Once HasFullName has been mixed into Speaker, with const grace = new Speaker("Grace", "Hopper"):

ExpressionResultWhy
grace.fullName"Grace Hopper"The getter runs when you read the property, with this as grace.
grace.initials()"GH"Methods work the same way.
Object.hasOwn(grace, "fullName")falseIt lives on Speaker.prototype, shared by every speaker, not on each one.
grace instanceof EventTargettrueSpeaker still has its one parent.

Mixins share behaviour between classes that can't share a parent. The cost: when a class mixes in several, it's harder to see where a method comes from, and two mixins with a method of the same name clash, so one replaces the other. Vue components can take a mixins option that shares options between components in a similar way.

mixin(Target, ...sources) takes a class and any number of plain objects. It copies every own property of each source, symbol keys included, onto Target.prototype, and returns Target.

Getters and setters must keep working as accessors: reading or writing the property on an instance runs them.

When two sources have a property with the same name, the later source wins.

Make the copied properties non-enumerable, like methods written in a class body. Never overwrite Target.prototype.constructor, even when a source has a constructor property.

Write this
function mixin(Target, ...sources) {
  // your code
}
 
// const HasFullName = {
//   get fullName() { return `${this.first} ${this.last}`; },
//   set fullName(value) { [this.first, this.last] = value.split(" "); },
// };
// class User { constructor(first, last) { this.first = first; this.last = last; } }
// mixin(User, HasFullName);     // returns User
// const user = new User("Ada", "Lovelace");
// user.fullName;                // "Ada Lovelace"
// user.fullName = "Grace Hopper";
// user.first;                   // "Grace": the setter ran
// Object.keys(User.prototype);  // []: the copied properties aren't enumerable

The answer

Write it yourself first. Then compare it with a reference solution and the checks it passes.

Checking your sign-in…