Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript class and invocation context

Can someone help me explain why the first example works, but the second doesn't.

Example 1:

// Define a class
function Foo(name) {
  var self = this;

  this.name = name;
  this.greeting = function() {
    console.log('Hello ' + self.name);
  }
}

var foo = new Foo('foo');
foo.greeting();

var greeting = foo.greeting;
greeting();

Output:

Hello foo
Hello foo

Example 2:

// Define a class
function Foo(name) {
  this.name = name;
}

// Greeting
Foo.prototype.greeting= function () {
  console.log('Hello ' + this.name);
}

var foo = new Foo('foo');
foo.greeting();

var greeting = foo.greeting;
greeting();

Output:

Hello foo
Hello undefined

My guess would be because the first example is using closure, so it retains the reference to the name local variable, but the second example is not, and since greeting() method is invoked without object context, it defaults to undefined.

like image 615
realguess Avatar asked Dec 17 '22 04:12

realguess


2 Answers

Many answers, all with useful and correct information, but none of them explains the behavior correctly.

In your first example, it only logs Hello foo both times, because you're creating a variable self which references the this object. That self variable is then closured in your greeting function. Hence, you can call that function however you want, it'll always access self.name and not this.name so its always the same.

You don't do that in your prototype example. There you directly access this.name and then it is indeed important of how the function is invoked (see @lwburk answer).

So again, even if you call the first example like

foo.greeting.call( window );

it would still access the closured self variable and log Hello foo.

like image 63
jAndy Avatar answered Dec 18 '22 19:12

jAndy


There are four ways to invoke a function in JavaScript, each of which changes the value of this inside the function:

  1. As a global function call: greeting(). The value of this is the global window object (in browsers).

  2. As a method on some object: foo.greeting(). The value of this is the object instance on the left-hand side of the . operator (foo).

  3. As a constructor: new greeting(). The value of this is a new object that is created and implicitly returned from the function. This is used to create new objects.

  4. Using call or apply: greeting.apply(someVal, someArgs). The value of this is the object passed as the first argument (someVal).

Any function can be executed in any of these four ways (although not all functions should be executed in some of them).

In the first case you're performing a method call:

var foo = new Foo('foo');
foo.greeting();

...so this is foo inside the function. In the second case you're performing a global function call:

var greeting = foo.greeting;
greeting();

...so this is window inside the function.

Edit: Note @jAndy's answer which points out the more salient issue in this case, which is that greeting closes over self in the first example (regardless of how it's called), but does not in the second (which then makes how the function is called relevant).

like image 35
Wayne Avatar answered Dec 18 '22 19:12

Wayne