3

JavaScript defines Object.prototype as a way to define methods or properties for class-like objects.

A big problem that exists when using Object.prototype is the incorrect behavior when a method is invoked via a callback as this will have the invoking context.

function Foo() {}

Foo.prototype.resize() {
  console.log(this);
}

var foo = new Foo();
window.on('resize', foo.resize);

So to work around this limitation we can do the following.

function Foo2() {
  function resize() {
    console.log(self);
  }

  var self = this;
  this.resize = resize;
}

var foo2 = new Foo2();
window.on('resize', foo2.resize);

Given the above situation, it seems like it's better to always define class-like objects in example 2.

Thus, when should we use the Object.prototype facilities? I mean, you cannot know a priori how your methods will be called, and most certainly you would want the correct this context reference when invoked.

Supporting context: I thought of this question as it seems like you are creating instance methods using Object.prototype. However, as illustrated, you are not. You are simply creating methods that have no "real" connection to their instance. Hence the need to use closure to create an instance-bound method. Furthermore, this question was thought of after reading this excellent explanation of this.

Community
  • 1
  • 1
James
  • 2,488
  • 2
  • 28
  • 45
  • 4
    Wouldn't you just do `window.on('resize', foo.resize.bind(foo));` ? The reason it's happening is because `this` is entirely dependant on execution context, and in an event handler the function is referenced and called by the handler. – adeneo Nov 02 '16 at 16:00
  • You could forgo the use of `this` entirely... – evolutionxbox Nov 02 '16 at 16:05
  • 1
    The biggest benefit of prototypes is the memory savings. You can create a million instances of a particular type, and if it uses prototypes, then there's still only one copy of each function. If you use the closure approach, then you will have a million slightly different copies of every function. The objects I define in JavaScript don't use prototypes or `this` because of all the confusion and bugs that `this` can cause, but I can't deny the memory-saving benefits of prototypes. – JLRishe Nov 02 '16 at 16:05
  • @JLRishe Your comment sounds like the beginnings of a good answer. If you can expound upon that answer it would be great. – James Nov 02 '16 at 16:07
  • @TechMedicNYC, is there anything I can add to make my answer acceptable? – Max Koretskyi Nov 03 '16 at 07:37

2 Answers2

3

Your confusion comes from the fact that you view JavaScript objects as having methods like in OOP languages. And so you believe they should have fixed context, since methods in OOP languages usually use "early binding" and so they are bound to the correct context. It's different in JavaScript. It's sort of "late binding", when the context of the function - this - is determined when the function is executed. To me, it's beneficial to see methods as simply object properties pointing to a function, that indeed can be executed in different contexts. For example, we could have something like this:

function resize() {
  console.log(this);
}

resize.call(window);
resize.call(custom);

If you want, certainly you can achieve "early binding" using bind for example:

   function O() {
      this.resize = function() {}.bind(this);
   }

   var o = new O();

But that limits re-usability of the object methods. For example, this wouldn't be possible:

Array.prototype.slice.call(arguments)

You can read here for some suggestions on why methods are not bound even in ES6.

Prototypes are not static methods, they are created to enable memory efficient code reuse. As JLRishe pointed out, the biggest advantage of prototypes is memory usage reduction, since you can define one instance of a function on prototype, and have convenient access to it as object property that have the prototype in their prototype chain. But the prototype is just for convenience. Here is the example with resize without prototype:

// here only one instance of `resize` function is created
function resize() {
  console.log(this);
}

var o1 = {
  resize: resize
}

var o2 = {
  resize: resize
}

You are simply creating methods that have no "real" connection to their instance.

Correct, here is the example of a prototype with "methods" later to be used with different contexts:

var prototype = {
   resize: function() {
     console.log(this);
   }
}

var o1 = {
  resize: resize
}

Object.setPrototypeOf(o1, prototype);

var o2 = {
  resize: resize
}

Object.setPrototypeOf(o2, prototype);

I think JavaScript was built with idea in mind that functions are first-class objects, not that objects should have methods with correctly bound context.

Static methods are usually implemented as properties on function constructors, like this:

function SomeObjectConstructor() {}
SomeObjectConstructor.someStaticMethod = function() {}
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Definitely some confusion prior to reading the answer I linked in my post. From an OOP standpoint `Object.prototype` seems analogous to `static methods` definition. And while static methods are undoubtedly useful, we have to do some acrobatics to get the context correct. It seems strange that JavaScript did not include a way to get the instance (other than through acrobatics--*bind*, *closure*, etc...). – James Nov 02 '16 at 16:21
  • 1
    No, `prototype` is a way to reuse code. Check the last part of my answer. Static methods is often implemented as properties of a function constructor. Let me add this to the answer – Max Koretskyi Nov 02 '16 at 16:25
  • 1
    some insights are [here](https://esdiscuss.org/topic/why-are-es6-class-methods-not-automatically-bound-to-the-instance) – Max Koretskyi Nov 02 '16 at 16:56
1

I think that directly binding an object's method to an event handler, while possible, is a shortcut you want to avoid.

window.on('resize', function () {
    foo.resize();
});

Even though it's more verbose, I think writing your handlers like this is clearer and wont affect the context of this in your Object.prototype methods.

As stated in one of the comments, using the object's prototype is more efficient than defining methods for each instance of your object.

Ben Guest
  • 1,488
  • 2
  • 17
  • 29