Grinn
Grinn

Reputation: 5543

Using bind to force the context of a prototype function

I'm working on a bit of JavaScript code that will be used to build constructors for "classes" (specifically View Models) for our enterprise-level software. One of the things that I'm doing is establishing a code-enforced design pattern, where the implementer will explicitly define what functions they want exposed by the instance of the class, and the code will add them to the prototype of the constructor, as opposed to properties of each instance of the class. This of course has the advantage of there only being one instance of each of these functions per type, as opposed to an instance for every instance.

Here's a CodePen of most of my examples.

The problem is that under certain conditions, I am dealing with binding loss issues. For example, with this constructor:

  function Foo(myName) {
    this.firstName = myName;
  }

  Foo.prototype.greet = function(yourName) {
    alert("Hello, " + yourName + ". I am " + this.firstName + ".");
  }

...this will work:

var sam = new Foo("Sam");
// Alerts "Hello, Denny. I am Sam."
sam.greet("Denny");

...but this will not:

var sad = new Foo("Sad");
// This changes the context of greet. :(
var sadGreet = sad.greet;
// Alerts "Hello, Denny. I am undefined."
sadGreet("Denny");

This is because when we do var sadGreet = sad.greet we are changing the context of the greet function to window, so this.firstName does not exist when calling sadGreet("Denny").

So, a solution that I came up with was to overwrite the prototype with a property that calls that prototype, wrapping it in Function.prototype.bind():

function Bar(myName) {
  this.firstName = myName;
  // Here's where the magic happens.
  this.greet = Bar.prototype.greet.bind(this);
}

Bar.prototype.greet = function(yourName) {
  alert("Hello, " + yourName + ". I am " + this.firstName + ".");
}

var happy = new Bar("Happy");
// Since each instance of Bar overwrites the context for greet, this will work. :)
var happyGreet = happy.greet;
// Alerts "Hello, Denny. I am Happy."
happyGreet("Denny");

My question is this: I assume that Bar is not quite as efficient as Foo, but does this completely void any benefit of declaring greet as a prototype method? Under the covers is it just duplicating greet as a property, anyway, or is my call to bind simply adding a "wrapper" for Bar.prototype.greet? In other words, is this effectively exactly the same as the above definition for Bar?

function Bar(myName) {
  this.firstName = myName;
  // Here's where the magic happens.
  this.greet = function(yourName) {
    alert("Hello, " + yourName + ". I am " + this.firstName + ".");
  }
}

Bonus points if you can not only answer the question but tell me how to test it!

Upvotes: 4

Views: 1051

Answers (1)

wmock
wmock

Reputation: 5492

Bar is definitely less efficient than Foo but not in terms of time complexity. Instead, it's less efficient in terms of memory consumption since each instance of Bar will now have a UNIQUE and NEW 'greet' function object (the 'bind' function creates a new function object).

Looking at your code, it actually becomes slightly convoluted to even have the 'greet' function on Bar.prototype. Instead, this works as well:

function Bar(myName) {
  this.firstName = myName;
  this.greet = greet.bind(this);
}

var greet = function(yourName) {
  alert("Hello, " + yourName + ". I am " + this.firstName + ".");
};

var happy = new Bar("Happy");
var happyGreet = happy.greet;
happyGreet("Denny");

You can test that the 'greet' function is not identical to each instance's 'greet' function by simply trying this:

console.log( greet === happyGreet );

Upvotes: 2

Related Questions