curveball
curveball

Reputation: 4505

Interesting JavaScript inheritance pattern

I have recently watched a video where Douglas Crockford was explaining inheritance patterns of Javascript. The video itself is pretty old - it was filmed 6 years ago - but still useful. In that video he showed one inheritance pattern he kinda invented (although I am not sure who the author is). This is the code using his approach:

// imitation of new operator
function objectConstructor(obj, initializer, methods) {
    // create prototype
    var func, prototype = Object.create(obj && obj.prototype);
    // add methods to the prototype
    if(methods) Object.keys(methods).forEach(function(key) {
        prototype[key] = methods[key];
    });
    // function that will create objects with prototype defined above
    func = function() {
        var that = Object.create(prototype);
        if(typeof initializer === 'function') initializer.apply(that, arguments);
        return that;
    }
    
    func.prototype = prototype;
    prototype.constructor = func;
    return func;
}

var person = objectConstructor(Object, function(name) {
    this.name = name;
},  {
    showName: function() {
        console.log(this.name);
    }
});
var employee = objectConstructor(person, function(name, profession) {
    this.name = name;
    this.profession = profession;
},  {
    showProfession: function() {
        console.log(this.profession);
    }
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver

Unfortanately, he didn't show the invocation. So, this part

var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName();
employeeInfo.showProfession();

is mine. It generally works, but it turns out that I repeat this.name = name; for both "classes" - person and employee. I played around but I didn't manage to make it work properly without that repetition. Seems I cannot get name because such a property isn't contained in the prototypal chain for employee. I didn't succeed either in mixing in stuff like person.call(this, arguments). So, apart from whether it is cool/nice/smart/sensible etc. or not in 2017, how could I remove this.name = name; from employee and get the same result? Or everything is ok and this approach doesn't suppose it?

Upvotes: 0

Views: 60

Answers (2)

Keith
Keith

Reputation: 24221

Here is your snippet with 2 small modifications so that you can do a super(name) type of call.

I've placed comments were I've made the modifications.. with prefix keith:

// imitation of new operator
function objectConstructor(obj, initializer, methods) {
    // create prototype
    var func, prototype = Object.create(obj && obj.prototype);
    // add methods to the prototype
    if(methods) Object.keys(methods).forEach(function(key) {
        prototype[key] = methods[key];
    });
    // function that will create objects with prototype defined above
    func = function() {
        var that = Object.create(prototype);
        if(typeof initializer === 'function') initializer.apply(that, arguments);
        return that;
    }
    
    func.prototype = prototype;
    //keith: store the initialization in constructor,
    //keith: as func is already creating the object..
    prototype.constructor = initializer;
    return func;
}

var person = objectConstructor(Object, function(name) {
    this.name = name;
},  {
    showName: function() {
        console.log(this.name);
    }
});
var employee = objectConstructor(person, function(name, profession) {
    //keith: call our super  person(name)
    person.prototype.constructor.call(this, name);
    this.profession = profession;
},  {
    showProfession: function() {
        console.log(this.profession);
    }
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver

Upvotes: 1

maazadeeb
maazadeeb

Reputation: 6112

Since the func constructor completely disregards this, passing any context to it via call or apply will not work. Creating a way to copy over the super class' properties after creating an object is one of the ways you could accomplish your task.

// imitation of new operator
function objectConstructor(obj, initializer, methods) {
    // create prototype
    var func, prototype = Object.create(obj && obj.prototype);
    // add methods to the prototype
    if(methods) Object.keys(methods).forEach(function(key) {
        prototype[key] = methods[key];
    });
    // function that will create objects with prototype defined above
    func = function() {
        var that = Object.create(prototype);
        if(typeof initializer === 'function') initializer.apply(that, arguments);
        return that;
    }
    
    func.prototype = prototype;
    prototype.constructor = func;
    return func;
}

function copyProperties(source, target) {
  for (var prop in source) {
    if (source.hasOwnProperty(prop)) {
      target[prop] = source[prop];
    }
  }
}

var person = objectConstructor(Object, function(name) {
    this.name = name;
},  {
    showName: function() {
        console.log(this.name);
    }
});
var employee = objectConstructor(person, function(name, profession) {
    copyProperties(person.apply(null, arguments), this);
    this.profession = profession;
},  {
    showProfession: function() {
        console.log(this.profession);
    }
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver

Upvotes: 1

Related Questions