Mixin

The design of the JavaScript language is single inheritance, that is, subclasses can only inherit one parent class, and multiple parent classes are not allowed. This design ensures that the hierarchical structure of object inheritance is tree-like, rather than complex network structure.

However, this greatly reduces the flexibility of programming. Because in actual development, it is sometimes inevitable that subclasses need to inherit multiple parent classes. For example, "cats" can inherit "mammals" or "pets".

Various single inheritance programming languages ​​have different multiple inheritance solutions. For example, the Java language also allows subclasses to inherit only one parent class, but also allows multiple interfaces to be inherited, which indirectly realizes multiple inheritance. Interface is a class like the parent class, except that it only defines the interface (method signature) and does not define the implementation, so it is also called an "abstract class". All methods inherited from Interface must be defined and implemented by themselves, otherwise an error will be reported. This avoids the biggest problem of multiple inheritance: naming collision of methods with the same name of multiple parent classes.

The JavaScript language does not adopt the Interface solution, but implements the introduction of methods from other classes through delegation.

var Enumerable_first = function () {
  this.first = function () {
    return this[0];
  };
};

var list = ["foo", "bar", "baz"];
Enumerable_first.call(list); // explicit delegation
list.first(); // "foo"

In the above code, list is an array, and there is no first method. Through the call method, the methods in Enumerable_first can be bound to list, so that list has the first method. This is called "delegation", and the list object delegates the first method of Enumerable_first.

Meaning

The name Mixin comes from ice cream. The basic flavor of ice cream is mixed with other flavors, which is called Mix-in.

It allows injecting some code into a class, so that the functionality of one class can be "mixed into" another class. It is essentially a solution to multiple inheritance, but it avoids the complexity of multiple inheritance and is conducive to code reuse.

Mixin is a normal class, which not only defines the interface, but also defines the implementation of the interface.

Subclasses achieve the purpose of multiple inheritance by binding methods on the this object.

Many libraries provide Mixin functions. Let's take Lodash as an example.

function vowels(string) {
  return /[aeiou]/i.test(this.value);
}

var obj = { value: "hello" };
_.mixin(obj, { vowels: vowels });
obj.vowels(); // true

The above code makes the obj object inherit the vowels method through the _.mixin method of the Lodash library.

The similar method of Underscore is _.extend.

var Person = function (fName, lName) {
  this.firstName = fName;
  this.lastName = lName;
};

var sam = new Person("Sam", "Lowry");

var NameMixin = {
  fullName: function () {
    return this.firstName + "" + this.lastName;
  },
  rename: function (first, last) {
    this.firstName = first;
    this.lastName = last;
    return this;
  },
};
_.extend(Person.prototype, NameMixin);
sam.rename("Samwise", "Gamgee");
sam.fullName(); // "Samwise Gamgee"

The above code uses the _.extend method, on the sam object (to be precise, on its prototype object Person.prototype), mixed with the NameMixin class.

The implementation of the extend method is very simple.

function extend(destination, source) {
  for (var k in source) {
    if (source.hasOwnProperty(k)) {
      destination[k] = source[k];
    }
  }
  return destination;
}

The above code adds all the methods of the source object to the destination object.

Trait

Trait is another multiple inheritance solution. It is very similar to Mixin, but there are some subtle differences.

  • Mixin can include state, but trait does not, that is, the methods in trait are not related to each other and can be linearly included. For example, Trait1 contains methods A and B, Trait2 inherits Trait1, and also contains its own method C, which is actually equivalent to directly including methods A and B , C.
  • For collisions of methods with the same name, Mixin contains resolution rules, while Trait reports errors.