JohnMerlino
JohnMerlino

Reputation: 3928

accessing prototype property without using "new" to instantiate function object

In Crockford's book "Good Parts", he mentions to avoid using "new" because it is an anti-pattern, since the language is prototypal and not classical. However, using his advice, I am unable to use the prototype property. I tried several different things:

// In the below example, we are unable to access the prototype property, because while parent is initialized with a function object, which has access to prototype property, when parent is executed, we return an object literal, which does not have access to that property. The error we get below is "TypeError: p.info is not a function". That happens because info is undefined for p, since p is an object literal. It is parent that has the prototype property.

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = {};

    that.name = function(){
            return name;
    }

    that.age = function(){
        return age;
    }
    return that;
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// Here we suffer from the same problem above:

var parent = function(name, age){
    var name = name || "";
    var age = age || "";

    return {
        name: function(){
            return name;
        },
        age: function(){
            return age;
        }
    }
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// this won't work either because "p" will be undefined since the return value of the function is undefined. Besides, "this" would refer to the global object, which is window in browsers.

var parent = function(name, age){
    var name = name || "";
    var age = age || "";

    this.name = function(){
        return name;
    }
    this.age = function(){
        return age;
    }
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// But using the "new" keyword to construct the object allows us to access the prototype. That must mean that the return value of Parent is a function and not a regular object. According to Stoyan Stefanov in his book "Javascript Patterns", when using the new keyword, under the covers a blank object is created which inherits from Parent's (the function) prototype: Object.create(Person.prototype). And then all references of "this" is attached to that object and it is returned.

var Parent = function(name, age){
    var name = name || "";
    var age = age || "";

    this.name = function(){
        return name;
    }
    this.age = function(){
        return age;
    }
}

Parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = new Parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// Unfortunately, I cannot simulate this. I get the error: "TypeError: this.prototype is not an object or null". Obviously, at the point of use, "this" is not parent yet.

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = Object.create(this.prototype);

    that.name = function(){
        return name;
    }
    that.age = function(){
        return age;
    }

    return that;
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info())

So when Crockford says avoid using "new", how are we supposed to add properties to prototype?

Upvotes: 2

Views: 819

Answers (2)

Panos Matzavinos
Panos Matzavinos

Reputation: 86

Using the Object.create() function you can do it like this:

// base, its prototype will be used to create a new parent
function Parent(name, age) {
    this.name = name || '';
    this.age = age || '';
};

// factory to create a new parent
function createParent(name, age) {
    var proto = Parent.prototype;
    var properties = {
        name: {writable: true, configurable: true, value: name || ''},
        age: {writable: true, configurable: true, value: age || ''}
    };
    var parent = Object.create(proto, properties);
    return parent;
}

// augment the Parent's prototype
Parent.prototype.info = function(){
    return "name: " + this.name + " age: " + this.age;  
}

// Create a new Parent
var p = createParent("John",25);

// Test
console.log("name: " + p.name + " age: " + p.age + " info: " + p.info());

You could check here for more options/variations on how to use the Object.create() function.

You can achieve similar functionality with different approaches depending on your needs. A simple example:

var parentBase = {};
var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = parentBase;

    that.name = function(){
            return name;
    }

    that.age = function(){
        return age;
    }
    return that;
}

parentBase.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

You better use Object.create, because it is in the ECMAS 5 and offers you more options, flexibility and functionality that you do not have to implement by yourself. Object.create implements the pattern described by Douglas Crockford. If the platform doesnt's support it then you could make your custom implementation as Crockford suggested here.

You can use the Object.beget similar with the Object.create as i described above. You could try it:

if (typeof Object.beget !== 'function') {

     Object.beget = function (o) {

         var F = function () {};

         F.prototype = o;

         return new F();
     };
}

// base, its prototype will be used to create a new parent
function Parent(name, age) {
    this.name = name || '';
    this.age = age || '';
};

// factory to create a new parent
function createParent(name, age) {
    var proto = Parent.prototype;
    var parent = Object.beget(proto);
    parent.name = name || '';
    parent.age = age || '';
    return parent;
}

// augment the Parent's prototype
Parent.prototype.info = function(){
    return "name: " + this.name + " age: " + this.age;  
}

// Create a new Parent
var p = createParent("John",25);

// Test
console.log("name: " + p.name + " age: " + p.age + " info: " + p.info());

Upvotes: 1

guest271314
guest271314

Reputation: 1

Try (this pattern)

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    // add `info` as `var`
    var that = {}, info;

    // change method to function expression
    that.name = (function(){
            return name;
    }())
    // change method to function expression
    that.age = (function(){
        return age;
    }())
    return that;
// add `|| {}` , for accessing `parent.info`
} || {};

// add param `that`  to `parent.info` function
parent.info = function(that){
    return "name: " + that.name + " age: " + that.age;  
};

var p = parent("John",25); 
console.log("name: " + p.name + " age: " + p.age + " info: " + parent.info(p));

// var results = document.createElement("div");

// results.innerHTML = parent.info(p);

// document.body.appendChild(results);

jsfiddle http://jsfiddle.net/guest271314/v0tdtk80/

Upvotes: 0

Related Questions