4
function Developer(skill) {
    this.skill = skill;
    this.says = function() {
        alert(this.skill + ' rocks!');
    }
}
var john = new Developer('Ruby');
var func = john.says;
func();

I tried this example when I do this I get the following message undefined rocks! instead of Ruby rocks!. can u explain why is that.

Gabriel
  • 18,322
  • 2
  • 37
  • 44
Lahiru Prasanna
  • 1,012
  • 2
  • 15
  • 32
  • 1
    [How `this` works is extensively explained in the MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). It already starts with *"In most cases, the value of `this` is determined by how a function is called."* – Felix Kling Apr 08 '14 at 05:22
  • Maybe the following answer can help you out in understanding JavaScript prototype and constructor functions.http://stackoverflow.com/questions/16063394/prototypical-inheritance-writing-up/16063711#16063711 – HMR Apr 08 '14 at 08:16

6 Answers6

8

Function Execution Context and the this Keyword

JavaScript functions have an execution context at invocation time such that the this keyword is bound to the object they are invoked from. If you call john.says() the execution context of the function would have a this keyword that points to john. If you instead assign a global variable func to the method says found on the object john you have changed the execution context to the global object. When you invoke the func function, this dereferences to window (or undefined*) and since window.skill is undefined, says will coerce that value into a string to concatenate it with the string ' rocks!'.

How to guarantee execution context using bind

You can instead bind a copy of the function to an object (effectively locking it's context reference):

var func = john.says.bind(john);

How to guarantee execution context using a closure

Alternately you can close over the relevant bits by using a closure in your constructor:

function Developer(skill){
  var _this = this; // we keep a reference here
  this.skill = skill;
  this.says = function(){
    alert(_this.skill + ' rocks!'); 
    // when invoked _this refers to the context at construction
  }
  return this;
}

How to guarantee a value using a closure

You could just reference the skill value directly from the method and so not need the context at all:

function Developer(skill){
  // because skill is defined in this context, says will refer to this context
  // to get the value of the skill variable.
  this.says = function(){
    alert(skill + ' rocks!');
  }
}

How to guarantee an execution context at invocation time using call and apply

The final options are to to invoke the method with the context you want at invocation time:

func.call(john /*, optional arguments... */);
func.apply(john /*, optional arguments as an array */);

How to use prototypes to allow the dynamic execution context to set the right this

If we want to reuse a method between object instances or types but have the right execution context when invoked we can use the prototype property.

function Developer(skill){
  this.skill = skill;
  this.says();
}

Developer.prototype.says = function(){
  alert(this.skill + ' rocks!');
}

var john = new Developer("Ruby"); // alert("Ruby rocks!")
var tony = new Developer("JavaScript");  // alert("JavaScript rocks!")

More reading:

* "use strict" activates a special strict mode representing the future of JavaScript. This special strict executing environment will not resolve to the global object when a context has not been set, instead resolving this to the appropriately scoped value undefined.

Gabriel
  • 18,322
  • 2
  • 37
  • 44
2

Let's illustrate what is happening, by actually returning the object:

function Developer(skill) {
  this.skill = skill;
  this.says = function() {
    alert(this.skill + ' rocks!');
  }
  return this; //this is basically { skill: skill, says: function() {} }
}

var john = new Developer('Ruby');
var func = john.says; // all it knows is the function() part, has no knowledge of skill
func(); //undefined rocks!

Ok, so why are we getting undefined? Well, once we rip out the function, it no longer has context - it doesn't know what this is. If we were to do:

func.apply(john);

Then we are passing in John as the this parameter. A workaround, it to pass the value in to the function when we create it:

function Developer(skill) {
  this.skill = skill;
  this.says = function(skill) { //<--calling this function
                return function() { //<--returning this function
                   alert(skill + ' rocks!');
                };
              }(this.skill); //<--passing in the value here
  return this;
}
dave
  • 62,300
  • 5
  • 72
  • 93
  • When an object is create via the `new` keyword the invocation implies that the newly created context object will be returned. So you do not "need" to return this at the bottom of the Developer method (though it never hurt to be safe). – Gabriel Apr 08 '14 at 05:35
1

Because func() only has the function and not any other related info.(this.skill)

The context is lost. this referred to the john when the says() is called.

But now, when you call func() this refers to the window. Therefore this.skills will return undefined unless you have a global variable of the same name.

Amit Joki
  • 58,320
  • 7
  • 77
  • 95
  • *"`this` no longer refers to the `john` object"* To be pedantic, it never referred to `john` in the first place, because it doesn't even exist until the function is called. – Felix Kling Apr 08 '14 at 05:28
  • @FelixKling, I've updated my answer. BTW, what is the meaning of "To be pedantic"? – Amit Joki Apr 08 '14 at 05:34
  • Oh, I just meant that I'm criticizing a tiny thing here that other people might not even notice ;) – Felix Kling Apr 08 '14 at 06:43
1
  1. When you call a function, this will refer to the object on which the function was called.

  2. When you say

    var func = john.says;
    

    you are just getting the function object (it doesn't remember the object on which it is defined).

  3. When you invoke the function, like this

    func();
    

    there is no current object, on which func is invoked. So, by default, JavaScript assigns the global object(window object in browser) as this. Since, skill is not defined in the global object, undefined is returned.

Note: In Strict mode, this will be undefined, if there is no current object.

That is why we have to explicitly bind the object to the function, like this

func.bind(john)();

Function.prototype.bind will return a new function, which is bound to john. So, when you invoke this this will refer john.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
0

the fastest way to keep the above pattern is to bind this direct to the method this.says = function () { ... }.bind(this);:

function Developer(skill) {
    this.skill = skill;
    this.says = function () {
        alert(this.skill + ' rocks!');
    }.bind(this);
}
var john = new Developer('Ruby');
var func = john.says;
func(); // Ruby rocks!
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
-1

try this

function Developer(skill) {
    this.skill = skill;
    this.says = function() {
        alert(this.skill + ' rocks!');
    }
}
var john = new Developer('Ruby');
john.says();

working demo http://jsfiddle.net/ZENtL/

Ankush Jain
  • 5,654
  • 4
  • 32
  • 57