AlicanC
AlicanC

Reputation: 893

"this" refers to something else?

When I run this code:

var Test = function() {
    return this.stuff;
};

Test.stuff = 'Neat!';

document.write(Test() || 'Not neat.');

Why do I get 'Not neat.'? Why can't I access the stuff property using this.stuff?

Upvotes: 2

Views: 230

Answers (5)

user166390
user166390

Reputation:

While other people have posted why this occurs (the understanding of this is incorrect), here is one solution which will work reliably.

Update: As Raynos noted, when using strict mode functions in ECMAScript 5th Edition, it is invalid to use arguments.callee (it will throw a TypeError). Thus caution should be exercised if using this approach. (When using a [correct] ECMAScript 5th edition engine, there is no reason to use arguments.callee over the name given to the function which is bound to the new scope -- see the end of the answer.)

var Test = function() {
   // arguments.callee is the current function, if any
   return arguments.callee.stuff
}
Test.stuff = 'Neat!'
alert(Test() || 'Not neat.') // Neat!

Another is to use a closure:

var Test = (function () {
  function fn () {
    // closure over fn, which names this function-object
    return fn.stuff
  }
  fn.stuff = 'Neat!' // here
  return fn          // do not combine with function declaration!
})()
Test.stuff = 'Neat!' // or here
alert(Test() || 'Not neat.') // Neat!

Or, a closure over a variable directly:

var Test = (function () {
  var stuff = 'Neat!'
  return function () {
    // variable closure, no property
    return stuff
  }
})()
alert(Test() || 'Not neat.') // Neat!

Or... so many ways.

Happy coding.


Another approach that was pointed out by Aadit M Shah is to use the function identifier to refer to the current function:

var Test = function Temp () {
   return Temp.stuff
}
Test.stuff = 'Neat!'
alert(Test() || 'Not neat.') // Neat! (But see below.)

As Aadit points out, this is valid, as per the ECMAScript 5th edition specification, page 99:

The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

However, some browsers (at least IE9) implements this incorrectly (and I am not sure if the above noted behavior is well-defined in the 3rd edition). Consider:

var x = function y () { return y }; y = 42; x();

In IE9 it will yield 42 and in FF8 it will yield the function-object. IE9 is incorrect here as it introduces y as a variable in the enclosing scope, which is forbidden by ECMAScript for function expressions. Here is an in-context example of how this incorrect implementation can lead to different results:

var Test = function Temp () {
   return Temp.stuff
}
Test.stuff = "Neat!"
Temp = {}
alert(Test() || 'Not neat.') // 'Not neat.' in IE9, 'Neat!' in FF8

Upvotes: 3

hugomg
hugomg

Reputation: 69944

Class methods and variables go on the prototype property:

Test.prototype.stuff = 'Neat!'

Constructor functions (I'm assuming this is what you want, given the capital case and the this) should be invoked with the new operator:

new Test()

and they should not return a value (you should instead use the default that returns this)

function Test(){
    this.instanceVariable = 17;
    //no return!
}

As for your real need , you can just access the function and its properties directly then

function Test(){
    return Test.stuff;
}

However I am not a big fan of abusing functions for namespaces like that. I prefer to have a namespace object for doing things

//in a real case I would probably use the module pattern for private variables
//but whatever...

var Namespace = {};

Namespace.stuff = 'Neat!';

Namespace.F = function(){
    console.log(Namespace.stuff);
};

Upvotes: 5

Aadit M Shah
Aadit M Shah

Reputation: 74204

This is what you have done:

var Test = function() {                //Test is a Function object
    return this.stuff;                 //this is a pointer to an object, not Test
};

Test.stuff = 'Neat!';                  //Add a property to Test

document.write(Test() || 'Not neat.'); //this has no property stuff

Change the last line of your code to:

document.write(Test.call(Test) || 'Not neat.'); //this now points to Test

The reason your code didn't work is because the this pointer points:

  1. The instance of the constructor created when the function call is prefixed with the new keyword. (e.g. var foo = new Foo(); //the this in Foo points to foo [for the sake of explanation]).
  2. The object passed to the call and apply functions as the first parameter.

What you want to do instead is something like:

var Test = function Temp() {           //Test is a Function object, alias Temp
    return Temp.stuff;                 //Temp is the same as Test, only locally
};

Test.stuff = 'Neat!';                  //Add a property to Test

document.write(Test() || 'Not neat.'); //writes Neat!

Upvote this answer if you liked it. Cheers.

Upvotes: 5

Ray Toal
Ray Toal

Reputation: 88388

You called Test from the global context, so this refers to the global object, so this.stuff refers to the global variable stuff which is undefined which is falsy. This is why you saw "Not neat."

You can make it show Neat! like this:

See http://jsfiddle.net/aYh5y/

window.stuff = 'Neat!';
var Test = function() {
    return this.stuff;
};
alert(Test() || 'Not neat.');

ADDENDUM

Here is a way you can make this work as an enclosing object:

var Test = function () {
    return this.stuff;
};

var example = {
    g: Test,
    stuff: "Pretty neat"
};

alert(example.g() || 'Not neat.');

Here we're calling Test through a target object.

http://jsfiddle.net/aYh5y/1/

Upvotes: 3

Rich O'Kelly
Rich O'Kelly

Reputation: 41767

The stuff property is assigned to the function, but read from the global this ... The two are seperate entities

Upvotes: 0

Related Questions