1. Background

Combinatorial inheritance is the most commonly used inheritance in JS, which is more likely to be encountered in actual projects. Recently, I encountered this on a project, but I didn’t recognize it at first. It was strange to see code that involved composite inheritance, and my gut told me there was a design pattern. Open the little Red Book prototype chain chapter, as expected appeared four characters [combination inheritance]. This baby likes to talk about things, so we are going to talk about combination inheritance with project practice.

A form consists of several controls, such as text controls, date controls, etc. These controls all have similar behaviors, such as reading and writing data, but the details of reading or writing are different. For example, date control should be processed according to format when reading or writing data, and text control has requirements on the length of text written. But no matter what control, some behaviors are the same such as checking whether the control is null.

In such a scenario, you can define the general operations of the control on the base class of the control, and subclasses can override them based on their own situation.

2. Design ideas

2.1 Definition of base class controls

function BaseControl(options) {
  this.controlKey = options.controlkey;
  this.dataField = options.dataField;
}

BaseControl.prototype.$getValue = () = >{}; BaseControl.prototype.$setValue =() = >{};Copy the code

2.2 Definition of text controls

function FormTextBox() {
}

FormTextBox.prototype.$getValue = function getValue(val) {
    return this.filter(val);
}

FormTextBox.prototype.$setValue = function setValue(val) {
    if (val.length > 2000) {
        return val.substr(0.2000);
    } else {
        returnval; }}// Filter sensitive words
FormTextBox.prototype.$filter = function filter(value) {... }Copy the code

2.3 Definition of factory function

function controlFactory(formInstance, updateView) {

  function IBaseControl() {}

  IBaseControl.prototype = {
    formInstance: formInstance, // Form instance
    updateView: updateView, // View update
  }

  function IControl() {}

  const controlProto = new IBaseControl();

  IControl.prototype = Object.assign(controlProto, BaseControl.prototype);

  return function impControl(options) {

    function IFormControl() {
      // Borrow the constructor
      BaseControl.call(this, options);
      // Pay attention to the call order
      FormTextBox.call(this, options);
    }

    const formControlProto = new IControl();
    
    IFormControl.prototype = Object.assign(formControlProto,FormTextBox.prototype);

    return newIFormControl(options); }}Copy the code

2.4 Borrow constructors

    function IFormControl() {
      // Borrow the constructor
      BaseControl.call(this, options);
      // Pay attention to the call order
      FormTextBox.call(this, options);
    }
Copy the code

This function is the core function. The BaseControl constructor and the FormTextBox constructor are called inside the IFormControl constructor. Notice that the function context is the function context of IFormControl, and the BaseControl constructor and FormTextBox constructor are just plain old functions. By borrowing constructors, IFormControl has its own copy of the data attributes to complete property inheritance from the parent.

2.5 Analysis of prototype chain

The prototype object for IFormControl is an extended IControl instance

const formControlProto = new IControl();

IFormControl.prototype = Object.assign(formControlProto,FormTextBox.prototype)
Copy the code

The IControl prototype object is an extended instance of IBaseControl

  const controlProto = new IBaseControl();

  IControl.prototype = Object.assign(controlProto, BaseControl.prototype);
Copy the code

The prototype object for IBaseControl is an object that has a form instance and a form update method

  IBaseControl.prototype = {
    formInstance: formInstance, // Form instance
    updateView: updateView, // View update
  }
Copy the code

So IFormControl -> IControl -> IBaseControl -> {formInstance, updateView}

Test cases

describe('Combinatorial inheritance'.() = > {
  const formInstance = {};
  const updateView = () = > {};
  const options = {
    controlkey: 'FormTextBox'.dataField: 'F00002'.visible: true};const impControl = controlFactory(formInstance, updateView);
  const formTextBox = impControl(options);

  test('formTextBox not instanceof FormTextBox'.() = > {
    expect(formTextBox instanceof FormTextBox).toBeFalsy();
  });

  test('formTextBox not instanceof BaseControl'.() = > {
    expect(formTextBox instanceof BaseControl).toBeFalsy();
  });
});
Copy the code

4. Conclusion analysis

The newly created text control instance formTextBox is of type neither formTextBox nor BaseControl. The formTextBox is of type IFormControl -> IControl -> IBaseControl.

5. Theoretical endorsement

Borrow constructor: Calls the supertype constructor inside the subtype constructor.

The idea of composite inheritance is to use the stereotype chain to achieve inheritance of stereotype attributes and methods, and to achieve inheritance of instance attributes by borrowing constructors.

6. Reference materials

  • JavaScript Advanced Programming

7. Reference code

Prototype inheritance