Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern needed: create new object that returns an executeable function and inherits from a prototype

Scenario 1 - everything works:

var AwesomeObject = function()
{
   var self = this;
   self.whatstuff = 'really awesome';
}

AwesomeObject.prototype.doStuff = function()
{
   var self = this;
   console.log('i did '+self.whatstuff+' stuff');
   return self;
}

var awesome = new AwesomeObject(); //returns a new AwesomeObject
awesome.doStuff(); // prints 'i did really awesome stuff' on the console

Now i want it even awesomer:

var AwesomeObject = function()
{  
   var f = function() { console.log('i am awesome'); }
   var self = f;
   self.whatstuff = 'really awesome';
   return self;
}

AwesomeObject.prototype.doStuff = function()
{
   var self = this;
   console.log('i did '+self.whatstuff+' stuff');
   return self;
}

var awesome = new AwesomeObject(); //returns the interal f object
awesome(); // prints 'i am awesome'
awesome.doStuff(); // throws an error

new AwesomeObject should return an executable function itself, so that i can say 'awesome();'

but i want it to inherit the AwesomeObject.prototype, too.

adding self.prototype = AwesomeObject.prototype; does not help.

var AwesomeObject = function()
{  
   var f = function() { console.log('i am awesome'); }
   var self = f;
   self.whatstuff = 'really awesome';
   self.prototype =  AwesomeObject.prototype;
   return self;
}

ok i can copy the AwesomeObject.prototype functions - one after the other - into the scope of f

var AwesomeObject = function()
{  
   var f = function() { console.log('i am awesome'); }
   var self = f;
   self.whatstuff = 'really awesome';
   self.doStuff =  function() { AwesomeObject.prototype.doStuff.apply(self,arguments); }
   return self;
}

but i think there must be a better way, a better pattern, what is it?

this issue drives me crazy, help would be really appreciated.

in general: how to create a function object that

  • can be created with new
  • returns a function object that can be executed
  • inherits all properties and methods of a given prototype

?

is there a way?

thx Franz

like image 633
Franz Enzenhofer Avatar asked May 05 '11 09:05

Franz Enzenhofer


People also ask

How do you create an object with prototype?

With Object.create() , we can create an object with null as prototype. The equivalent syntax in object initializers would be the __proto__ key. By default properties are not writable, enumerable or configurable.

What is prototype design pattern in JS?

The Prototype design pattern relies on the JavaScript prototypical inheritance. The prototype model is used mainly for creating objects in performance-intensive situations. The objects created are clones (shallow clones) of the original object that are passed around.

How do you set the prototype of one object to another?

The Object. setPrototypeOf() method sets the prototype (i.e., the internal [[Prototype]] property) of a specified object to another object or null. All JavaScript objects inherit properties and methods from a prototype. It is generally considered the proper way to set the prototype of an object.

How do objects inherit from other objects in JavaScript?

When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.


3 Answers

A very simple pattern is a factory.

var AwesomeObject = (function() {
    var AwesomeObject = function() {
        this.whatstuff = 'really awesome';
    };

    AwesomeObject.prototype.doStuff = function() {
        console.log('i did ' + this.whatstuff + ' stuff');
        return this;
    };

    return function() {
        var o = new AwesomeObject();
        var f = function() { console.log("I am awesome"); };
        for (var k in o) {
            f[k] = o[k];    
        }

        return f;
    };

})();

var foo = AwesomeObject();
foo();
foo.doStuff();

Live Example.

The idea is that you seperate your function and your object into two things. Your object exists in the local scope of your function and the function can use the object.

The object itself inherits completely through the prototype.

The key is do forward all properties/methods of the object onto the function.

This is the cleanest solution.

like image 111
Raynos Avatar answered Nov 22 '22 15:11

Raynos


When a property is resolved the prototype chain is traversed as you probably know. But if you have an object awesome and try to evaluate awesome.doStuff, then awesome.prototype will never be queried for the property. You can verify this in your example, "doStuff" in awesome => false but "doStuff" in awesome.prototype => true.

So what you're doing is not changing the implicit properties of awesome, you are changing its prototype, meaning any objects created by doing new awesome will have that property. Verification: "doStuff" in new awesome() => true. And this makes sense, since there is no way to distinguish between a constructor or a regular function when using f/awesome.

The procedure when resolving a property p on an object o is roughly as follows:

  • Check whether p is defined on o
  • Check whether p is defined on o.__proto__ (usage of __proto__ is non-standard but widely implemented, except for jscript last time i checked and it has now been deprecated in SpiderMonkey)
  • Check whether p is defined on o.constructor.prototype
  • Check whether p is defined on o.constructor.prototype.prototype
  • etc

So one solution would be to simply set o.__proto__ = AwesomeClass.prototype. Think of __proto__ as a hidden intermediary object between an object and its prototype. Each instance receives its own unique __proto__ object. But this is deprecated and non-standard like I said.

We could also set the values in Function.prototype but that would override other Function properties and affect all Function instances. We don't want that.

So what's left? Not much it turns out. There is no way to set the complete prototype of an object while retaining it's inherited prototype. You will need to iterate through your prototype and copy all properties. Fortunately this will allow instanceof to behave as expected when working with chains of constructors, as well as allowing inheritance/overriding of properties properly.

The problem is really that there is no built-in way to copy the properties of an object into another one, and that there is no standard way to change an object's prototype chain ad-hoc (__proto__).

So use __proto__, or iterate through the prototype.

like image 24
Adam Bergmark Avatar answered Nov 22 '22 16:11

Adam Bergmark


I don't think there is a good way to do this. I would redesign your program to avoid it.

However, here is a bad, platform-dependent solution (works on V8 using non-standard __proto__ property):

var PrototypeToBeInherited = {'inheritedProperty': 'inheritedPropertyValue'};

f = function() {
  return "result";
};
f.__proto__ = PrototypeToBeInherited;

f()
 => "result";
f.inheritedProperty
 => "inheritedPropertyValue"

For your requirement that it must be created with "new", just wrap it in function:

F = function() {
    return f;
}
var instance = new F();
like image 42
Joel Avatar answered Nov 22 '22 15:11

Joel