In depth understanding of the JavaScript series (46) : code reuse patterns (recommended)

  • 2020-05-10 17:45:31
  • OfStack

introduce

The four code reuse patterns described in this article are best practices and are recommended for use in programming.

Pattern 1: prototype inheritance

Prototype inheritance is to make the parent object as the prototype of the child object, so as to achieve the purpose of inheritance:


function object(o) {
    function F() {
    }     F.prototype = o;
    return new F();
} // The parent object to inherit
var parent = {
    name: "Papa"
}; // A new object
var child = object(parent); // test
console.log(child.name); // "Papa"
// Parent constructor
function Person() {
    // an "own" property
    this.name = "Adam";
}
// Add new properties to the stereotype
Person.prototype.getName = function () {
    return this.name;
};
// Create a new person
var papa = new Person();
// inheritance
var kid = object(papa);
console.log(kid.getName()); // "Adam"
// Parent constructor
function Person() {
    // an "own" property
    this.name = "Adam";
}
// Add new properties to the stereotype
Person.prototype.getName = function () {
    return this.name;
};
// inheritance
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function", Because it's defined in the prototype
console.log(typeof kid.name); // "undefined", Because it only inherits the prototype

At the same time, ECMAScript5 also provides a similar method called Object.create is used to inherit objects and is used as follows:


/* Using the new version ECMAScript 5 Provided functions */
var child = Object.create(parent); var child = Object.create(parent, {
    age: { value: 2} // ECMA5 descriptor
});
console.log(child.hasOwnProperty("age")); // true

Also, properties can be defined more fine-grained on the second parameter:


// First, define 1 A new object man
var man = Object.create(null); // Next, create the configuration Settings that contain the properties
// Property to be writable, enumerable, configurable
var config = {
    writable: true,
    enumerable: true,
    configurable: true
}; // Usually use Object.defineProperty() To add a new property (ECMAScript5 Support)
// Now, for convenience, let's customize 1 Three encapsulation functions
var defineProp = function (obj, key, value) {
    config.value = value;
    Object.defineProperty(obj, key, config);
} defineProp(man, 'car', 'Delorean');
defineProp(man, 'dob', '1981');
defineProp(man, 'beard', false);

So, here's what inheritance does:


var driver = Object.create( man );
defineProp (driver, 'topSpeed', '100mph');
driver.topSpeed // 100mph

However, it is important to note that Object.create (null) creates an object whose prototype is undefined, which means there are no toString and valueOf methods, so alert(man); It can go wrong, but alert(man.car); No problem.

Pattern 2: copy all properties for inheritance

This method of inheritance is to copy all the properties of the parent object to the child object, 1 child object can use the data of the parent object.

Let's look at an example of a shallow copy:


/* Shallow copy */
function extend(parent, child) {
    var i;
    child = child || {};
    for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            child[i] = parent[i];
        }
    }
    return child;
} var dad = { name: "Adam" };
var kid = extend(dad);
console.log(kid.name); // "Adam" var dad = {
    counts: [1, 2, 3],
    reads: { paper: true }
};
var kid = extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString()); // "1,2,3,4"
console.log(dad.reads === kid.reads); // true

In the last line of code, you can see that the reads of dad and kid are identical, that is, they use the same reference, which is the problem with shallow copy.

Let's look at one more deep copy:


/* Deep copy */
function extendDeep(parent, child) {
    var i,
        toStr = Object.prototype.toString,
        astr = "[object Array]";     child = child || {};     for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            if (typeof parent[i] === 'object') {
                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
                extendDeep(parent[i], child[i]);
            } else {
                child[i] = parent[i];
            }
        }
    }
    return child;
} var dad = {
    counts: [1, 2, 3],
    reads: { paper: true }
};
var kid = extendDeep(dad); kid.counts.push(4);
console.log(kid.counts.toString()); // "1,2,3,4"
console.log(dad.counts.toString()); // "1,2,3" console.log(dad.reads === kid.reads); // false
kid.reads.paper = false;

After the deep copy, the two values are not the same, bingo!

Mode 3: mixing (mix-in)

Mixing is to copy one or more (or all) properties (or methods) of one object to another object. Let's take an example:


function mix() {
    var arg, prop, child = {};
    for (arg = 0; arg < arguments.length; arg += 1) {
        for (prop in arguments[arg]) {
            if (arguments[arg].hasOwnProperty(prop)) {
                child[prop] = arguments[arg][prop];
            }
        }
    }
    return child;
} var cake = mix(
                { eggs: 2, large: true },
                { butter: 1, salted: true },
                { flour: '3 cups' },
                { sugar: 'sure!' }
                ); console.dir(cake);

The mix function copies the child properties of all the parameters passed into the child object to produce a new object.

So how do we just want to mix in some properties? What should I do? In fact, we can use the extra parameters to define the needs with attributes, such as mix (child parent, method1, method2) so that you can only will parent method1 and infiltrate into child method2. The code:


// Car
var Car = function (settings) {
    this.model = settings.model || 'no model provided';
    this.colour = settings.colour || 'no colour provided';
}; // Mixin
var Mixin = function () { };
Mixin.prototype = {
    driveForward: function () {
        console.log('drive forward');
    },
    driveBackward: function () {
        console.log('drive backward');
    }
};
// The definition of the 2 Are the mixed objects ( reciving ) and the object from which it was mixed ( giving)
function augment(receivingObj, givingObj) {
    // If the specified method name is provided, the argument is redundant 3 a
    if (arguments[2]) {
        for (var i = 2, len = arguments.length; i < len; i++) {
            receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]];
        }
    }
    // If you don't specify no 3 Three arguments, or more, are mixed in with all the methods
    else {
        for (var methodName in givingObj.prototype) {
            // check receiving Object does not contain the name to be mixed inside, how to do so
            if (!receivingObj.prototype[methodName]) {
                receivingObj.prototype[methodName] = givingObj.prototype[methodName];
            }
        }
    }
} // to Car The properties are mixed, but the values are mixed 'driveForward' and 'driveBackward'*/
augment(Car, Mixin, 'driveForward', 'driveBackward'); // Create a new object Car
var vehicle = new Car({ model: 'Ford Escort', colour: 'blue' }); // Test for success in getting mixed methods
vehicle.driveForward();
vehicle.driveBackward();

This method is more flexible to use.

Pattern 4: borrow method

One object borrows one or two methods from another object, and there is no direct relationship between the two objects. No need to explain, just explain in code:


var one = {
    name: 'object',
    say: function (greet) {
        return greet + ', ' + this.name;
    }
}; // test
console.log(one.say('hi')); // "hi, object" var two = {
    name: 'another object'
}; console.log(one.say.apply(two, ['hello'])); // "hello, another object" // will say Assigned to 1 A variable, this Will point to a global variable
var say = one.say;
console.log(say('hoho')); // "hoho, undefined" // The incoming 1 The callback function callback
var yetanother = {
    name: 'Yet another object',
    method: function (callback) {
        return callback('Hola');
    }
};
console.log(yetanother.method(one.say)); // "Holla, undefined" function bind(o, m) {
    return function () {
        return m.apply(o, [].slice.call(arguments));
    };
} var twosay = bind(two, one.say);
console.log(twosay('yo')); // "yo, another object"
// ECMAScript 5 to Function.prototype added 1 a bind() Method so that it is easy to use apply() and call() . if (typeof Function.prototype.bind === 'undefined') {
    Function.prototype.bind = function (thisArg) {
        var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
        return function () {
            return fn.apply(thisArg, args.concat(slice.call(arguments)));
        };
    };
} var twosay2 = one.say.bind(two);
console.log(twosay2('Bonjour')); // "Bonjour, another object" var twosay3 = one.say.bind(two, 'Enchant e ');
console.log(twosay3()); // "Enchant e , another object"

conclusion

I don't have to summarize it.


Related articles: