4

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!

8
  • Questions about efficiency are usually tough for SO because it's hard to answer conclusively. That said, my primary reaction is that I'd consider happyGreet(...) in general to be a mistake. If a function is on an instance, you probably shouldn't go assigning it to a variable, if if you do, you should .bind it there at the assignment location, or do happyGreet.call(happy, "Denny") Commented Dec 30, 2014 at 20:58
  • @loganfsmyth Thanks for your response, and I agree. Unfortunately this is only one of several possible examples of how "binding loss" can occur - and I'm not necessarily in control of every implementation of these constructors. Commented Dec 30, 2014 at 21:00
  • A function's execution context includes a this parameter that is set by how the function is called, or using bind. If you change how you call a function, they you will likely change its this. Or not. Commented Dec 30, 2014 at 21:51
  • What are your criteria for efficient? Use less memory? Run faster? Require less code? How bind works is defined in ECMA-262 so you can answer some of the questions yourself. Commented Dec 30, 2014 at 21:59
  • 1
    @wmock—ECMA-262 defines behaviour, not implementation, so whether the function object is actually copied or just appears to be copied is moot and probably has very little effect on performance. A bigger question is why this strategy would be used at all. Why are programmers allowed to determine the methods that an object will have on the fly? Why hasn't the architect already determined the structure of objects and the methods they require? An object with an unused method on its [[Prototype]] has used zero additional memory and suffered no performance loss. So what's the point? :-/ Commented Dec 30, 2014 at 22:29

1 Answer 1

2

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 );
Sign up to request clarification or add additional context in comments.

7 Comments

In your example, wouldn't happy.greet also be a UNIQUE and NEW greet function as well?
No, happy.greet is simply a REFERENCE to the 'greet' function object that belongs to each instance of Bar. Basically the 'var greet' function is one function, and then each instance of Bar creates a new 'greet' function based off of the 'var greet' function.
Edited my response to show that greet is not the same function as happyGreet. This also works the same in your previous example: happyGreet !== Bar.prototype.greet
So is this effectively the same as this.greet = function (yourName) { greet.call(this, yourName); }; (where only that small, anonymous function would be replicated) or will using bind cause it to duplicate the entire greet function?
Yes, they are effectively the same since you're creating a new function that is using 'call' to execute the original 'greet' function. In both cases, memory will be allocated to create new greet functions for each instance and each of these new greet functions implicitly call the original 'greet' function (either via bind or call).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.