2

I have a simple constructor that has firstname and lastname.

function Parent(){
    this.firstName;
    this.lastName;
}

There are four functions defined in the prototype of that constructor which does it's own tasks.

Parent.prototype.flipName = function () {
    return this.lastName + ' ' + this.firstName;
}

Parent.prototype.setFirstName = function (name) {
    this.firstName = name;
}

Parent.prototype.setLastName = function (last) {
    this.lastName = last;
}

Parent.prototype.getFullName = function (callback) {
    // used alert for the sake of simplicity
    alert("Callback: " + callback());
    return this.firstName + ' ' + this.lastName;
}

To demonstrate this I have attached jsfiddle as well.

So my question is whenever I pass callback on getFullName function this somehow loses the context to the Parent object (johny in our case) and returns as undefined undefined. this however, works fine on getFullName function.

I am aware that this is pointing to the window object instead of the Parent in the callback but I can't seem to find the reason behind it.

shriek
  • 5,605
  • 8
  • 46
  • 75
  • 1
    See [`Fucntion.prototype.bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) – megawac Jan 15 '14 at 00:20
  • Not sure why this happens, but if you replace alert("Callback: " + callback()); with alert("Callback: " + callback.apply(this)); it will work. – aebabis Jan 15 '14 at 00:20
  • Related: [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback). – Felix Kling Jan 15 '14 at 00:23
  • *"I am aware that this is pointing to the window object instead of the Parent in the callback but I can't seem to find the reason behind it."* Because if you call a function "the normal way", i.e. `func()`, then `this` refers to the global object (or `undefined` in strict mode). See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this for more info. – Felix Kling Jan 15 '14 at 00:24
  • 1
    *this* is not "context", it is a property of an [execution context](http://ecma-international.org/ecma-262/5.1/#sec-10.4) that is set by how a function is called or by using [*bind*](http://ecma-international.org/ecma-262/5.1/#sec-15.3.4.5). – RobG Jan 15 '14 at 00:48
  • @megawac & @RobG I was able to do with bind without having to change much in my `prototype` This is what I have. Correct me if I am wrong. I like the alternative solution that others are giving too but I wanted to do this without changing much in my internal code. Again, correct me if this is not the good practice johny.getFullName(johny.flipName.bind(johny)) http://jsfiddle.net/8X5bs/ – shriek Jan 15 '14 at 01:34
  • @FelixKling Good point on the strict mode. – shriek Jan 15 '14 at 01:36

3 Answers3

1

jsFiddle Demo

The reason is that when you pass in the function pointer johny.flipName it is an anonymous function. You can see this by logging it to the console. As a result, the function will execute, but under the global (window) scope.

To get around this, you must (if you wish) preserve the scope of the callback. You can do this by using callMDN

Parent.prototype.getFullName = function (callback) {
// used alert for the sake of simplicity
 alert("Callback: " + callback.call(this));
 return this.firstName + ' ' + this.lastName;
};
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • You should usually use `call` instead of `apply` if you're not passing arguments – megawac Jan 15 '14 at 00:21
  • @megawac - Why is that? – Travis J Jan 15 '14 at 00:23
  • No better reason than its a simpler, faster operation and it costs one less byte in typing – megawac Jan 15 '14 at 00:26
  • @megawac - I will give the 1 byte. Faster? My test was inconclusive (but seems to favor call) http://jsperf.com/call-speed-versus-apply-speed . See my edit. – Travis J Jan 15 '14 at 00:30
  • Was looking for the specification, `apply` is a much more complex function call, see [http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3](http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3). I did not know the bit about undefined or null however so I guess it doesnt matter as it will defer to `func.call` anyway – megawac Jan 15 '14 at 00:40
  • 2
    The OP's issue has nothing to do with "anonymous" functions (i.e. function expressions) or scope. A function's *this* is set **entirely** by how the function is called or by using *bind*. – RobG Jan 15 '14 at 00:50
  • Marking this as correct as this had the explanation, reference and working example. – shriek Jan 15 '14 at 01:45
0

Not enough reputation to add a comment, so here is another answer. Travis has it right for how the question is asked. I think, however, that perhaps the actual use case is possibly a bit more complex. If you use

callback.call(this);

Your callback function is going to be executed with the value of "this" referencing the object that called getFullName. This may or may not be what you want "this" to refer to when you are inside your callback function. So take the following example.

function Parent() {
    var self = this;
    self.firstName;
    self.lastName;
}

function Child() {
    var self = this;
    self.firstName;
}

Child.prototype.getName = function() {
    return this.firstName;
}

Parent.prototype.getFullName = function (callback) {
    // used alert for the sake of simplicity
    alert("Callback: " + callback.call(this));
    return this.firstName + ' ' + this.lastName;
}
Parent.prototype.flipName = function () {
    return this.lastName + ' ' + this.firstName;
}
Parent.prototype.setFirstName = function (name) {
    this.firstName = name;
}

Parent.prototype.setLastName = function (last) {
    this.lastName = last;
}

var johny = new Parent();
johny.setFirstName("Johny");
johny.setLastName("Bravo");

var sue = new Parent();
sue.setFirstName("Sue");
sue.setLastName("Bravo");

// used alert for the sake of simplicity
alert("FullName: " + johny.getFullName(sue.flipName));

When you execute callback.call(this); you are actually going to get "Bravo Johny" when you are probably expecting "Bravo Sue".

I don't know if this is best practice or not, but what I would do instead would be:

alert("FullName: " + johny.getFullName(function() {
    return sue.flipName();
}));
Michael Stewart
  • 411
  • 2
  • 7
  • *this* has nothing to do with scope. It is essentially a local variable that references an object in non–strict mode, or any value in strict mode. – RobG Jan 15 '14 at 00:56
0

In global code, this always references the global object. In function code, the value of this is set by how the function is called (e.g. direct call, as a method, using new, call, apply or bind).

If you want this within the callback to have a certain value, then set it in the call using either call or apply, e.g.

callback.apply(this, args);

If the value of this is not set, it will default to the global object (window in a browser) or in strict mode it will be undefined.

RobG
  • 142,382
  • 31
  • 172
  • 209