Deep understanding of JavaScript inheritance features and best practices

Keywords: mycat Javascript Attribute

Inheritance is the pattern of code reuse. JavaScript can simulate class-based patterns and support other more expressive patterns. But keeping it simple is usually the best strategy.

JavaScript is a prototype-based language, that is, it can inherit other objects directly.

1 pseudo class

The prototype of JavaScript is not to inherit objects directly from other objects, but to insert a redundant indirect layer: to generate objects through constructors.

When a function is created, the function object generated by the Function constructor runs similar code:

this.prototype = {constructor : this};

The new function object adds a prototype attribute, which is an object that contains the constructor attribute and whose attribute value is the new function.

When a constructor invocation pattern is used, i.e. a function is called with new, it performs as follows:

Function.method('new', function (){
    var that = Object.create(this.prototype);//Create a new object that inherits the prototype object of the constructor function
    var other = this.apply(that, arguments);//Call the constructor function to bind this to the new object
    return (typeof other === 'object' && other) || that;//If the return value of the constructor function is not an object, the new object is returned directly.
});

We can define a constructor and then extend its prototype:

//Define constructors and extend prototypes
var Mammal = function (name) {
    this.name = name;
};

Mammal.prototype.get_name = function () {
    return this.name;
};

Mammal.prototype.says = function () {
    return this.saying || '';
};

Then an example is constructed:

var myMammal = new Mammal('Herb the mammal');
console.log(myMammal.get_name());//Herb the mammal

Construct another pseudoclass to inherit Mammal (define the constructor function and replace its prototype):

var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};
Cat.prototype = new Mammal();

Expanded prototype:

Cat.prototype.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};
Cat.prototype.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};
var myCat = new Cat('Henrietta');
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow

We use the method method method to define the inherits method to hide the ugly details above.

/**
 * New method Method method for Function.prototype
 * @param name Method name
 * @param func function
 * @returns {Function}
 */
Function.prototype.method = function (name, func) {
    if (!this.prototype[name])//Add without this method
        this.prototype[name] = func;
    return this;
};

Function.method('inherits', function (Parent) {
    this.prototype = new Parent();
    return this;
});

Both methods return this so that we can program O(__) in a cascade manner.~

var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
}.inherits(Mammal).method('purr', function (n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    }).method('get_name', function () {
        return this.says() + ' ' + this.name + ' ' + this.says();
    });
var myCat = new Cat('Henrietta');
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow

Although we have constructor functions that behave like "classes", there is no private environment, all attributes are public, and methods of the parent class cannot be accessed.

If you forget to add the new prefix when calling the constructor, this will not be bound to the new object, but to the global variable!!! In this way, we not only expand new objects, but also destroy the global variable environment.

This is a serious language design error! To reduce the probability of this problem, all constructor functions are conventionally named in capitalized form. So when we see a function in capitalized form, we know that it's the constructor function Lao O(_____)~

Of course, a better strategy is not to use constructor functions at all.

2 Object Descriptor

Sometimes, the constructor accepts a lot of parameters, which is troublesome. So when you write a constructor, it's better to have it accept a simple object descriptor:

var myObject = maker({
  first: f,
  middle: m,
  last: l
});

Now these parameters can be arranged in any order, and the constructor is smart enough to use default values for those parameters that are not passed in, and the code becomes easier to read as O(_____)~

3 prototype

Prototype-based inheritance means that a new object can inherit the attributes of an old object. First, a useful object is constructed, and then more objects similar to that object can be constructed.

/**
 * prototype
 */
var myMammal = {
    name: 'Herb the mammal',
    get_name: function () {
        return this.name;
    },
    says: function () {
        return this.saying || '';
    }
};
//Create a new instance
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};
myCat.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow

The create method is used here to create new instances:

Object.create = function (o) {
        var F = function () {
        };
        F.prototype = o;
        return new F();
 }

4 functionalization

The problem with inheritance patterns so far is that privacy cannot be protected and all attributes of objects are visible. Some ignorant programmers use the disguised private mode of giving an odd name to a property that needs to be private and hoping that other programmers who use code will pretend not to see them!

In fact, there is a better way: to apply the module mode.

First, we construct a function to generate an object, which has these steps:

  1. Create new objects. There are four ways:
    [1] Construct an object literal quantity.
    [2] Call a constructor function.
    [3] Construct a new instance of an existing object.
    [4] Call any function that returns an object.
  2. Define private instance variables and methods.
  3. Extend methods for this new object, which have privileges to access these parameters.
  4. Returns this new object.

The pseudocode of the functional constructor is as follows:

var constructor = function (spec, my){
   var that, other private variables;
   my = my || {};

  Add shared variables and functions to my

  that = a new object

  Privilege methods added to that

  return that;
};

The spec object contains all the information needed to construct a new instance, which can be used in private variables or other functions.
The my object provides a shared container for constructors in an inheritance chain, and if it is not passed in, a my object is created.

The way to create privileged methods is to define functions as private methods and then assign them to that:

var methodical = function (){
  ...
};
that.methodical = methodical;

The advantage of this two-step definition is that private methodical is not affected by this instance being changed.

Now, we apply this pattern to the mammal example:

var mammal = function (spec) {
    var that = {};
    that.get_name = function () {
        return spec.name;
    };
    that.says = function () {
        return spec.saying || '';
    };
    return that;
};

var myMammal = mammal({name: 'Herb'});
console.log(myMammal.get_name());//Herb

var cat = function (spec) {
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.purr = function (n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    };
    that.get_name = function () {
        return that.says() + ' ' + spec.name + ' ' + that.says();
    };
    return that;
};
var myCat = cat({name: 'Henrietta'});
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow

Functionalized schemas can also call methods of parent classes. Here we construct a superior method that returns a function that calls a method name:

//Returns a function that calls a method name
Object.method('superior', function (name) {
    var that = this,
        method = that[name];
    return function () {
        return method.apply(that, arguments);
    };
});

Now create a coolcat with a get_name that calls the parent method:

var coolcat = function (spec) {
    var that = cat(spec),
        super_get_name = that.superior('get_name');
    that.get_name = function (n) {
        return 'like ' + super_get_name() + ' baby';
    };
    return that;
};
var myCoolCat = coolcat({name: 'Bix'});
console.log(myCoolCat.get_name());//like meow Bix meow baby

Functional schemas have great flexibility and can better implement encapsulation, information hiding and access to parent methods.

If all the states of the object are private, it is called anti-counterfeiting object. The attributes of this object can be replaced or deleted, but the state of the object is not affected. If an object is created in a functional pattern and all methods of the object do not use this or that, then the object is persistent and will not be invaded. The internal state of the persistent object cannot be accessed unless there is a privileged method.

5 Event Handler

A function that can add simple event handling features to any object can be constructed. Here, we add an on method, fire method and private event registration object to this object:

var eventuality = function (that) {
    var registry = {};

    /**
     * Trigger event
     *
     * Event handlers registered with the'on'method will be invoked
     * @param It can be a string containing the name of an event, or an object with a type attribute (the value is the name of the event).
     */
    that.fire = function (event) {
        var array,
            func,
            handler,
            i,
            type = typeof event === 'string' ? event : event.type;

        //If the event has been registered, it is traversed and executed sequentially
        if (registry.hasOwnProperty(type)) {
            array = registry[type];
            for (i = 0; i < array.length; i += 1) {
                handler = array[i];//The handler contains a method and a set of optional parameters
                func = handler.method;
                if (typeof func === 'string') {//If the method is a string name, look for it
                    func = this[func];
                }

                //Call it. If the handler contains parameters, pass the past, otherwise pass the event object
                func.apply(this, handler.parameters || [event]);
            }
        }
        return this;
    };

    /**
     * Register an event
     * @param type
     * @param method
     * @param parameters
     */
    that.on = function (type, method, parameters) {
        var handler = {
            method: method,
            parameters: parameters
        };
        if (registry.hasOwnProperty(type)) {//If it already exists, add an array entry
            registry[type].push(handler);
        } else {//Newly added
            registry[type] = [handler];
        }
        return this;
    };
    return that;
};

Eventity can be invoked on any individual object and given event handling. It can also be called in the constructor before that is returned:

eventuality(that);

The weak type feature of JavaScript is a huge advantage here, because we don't have to deal with type O(__) in object inheritance relationships.~

Posted by misty on Thu, 23 May 2019 13:32:59 -0700