tags:

views:

133

answers:

5

I've been reading through quite a few articles on the 'this' keyword when using JavaScript objects and I'm still somewhat confused. I'm quite happy writing object orientated Javascript and I get around the 'this' issue by referring the full object path but I don't like the fact I still find 'this' confusing.

I found a good answer here which helped me but I'm still not 100% sure. So, onto the example. The following script is linked from test.html with <script src="js/test.js"></script>

if (!nick) {
    var nick = {};
}

nick.lowman = function(){
    var helloA = 'Hello A';
    console.log('1.',this, this.helloA);

    var init = function(){
        var helloB = 'Hello B';
        console.log('2.',this, this.helloB);
    }

    return {
        init: init
    }
}();

nick.lowman.init();

What kind of expected to see was

1. Object {} nick.lowman, 'Hello A'
2. Object {} init, 'Hello B'

But what I get is this?

1. Window test.html, undefined
2. Object {} init, undefined

I think I understand some of what's happening there but I would mind if someone out there explains it to me.

Also, I'm not entirely sure why the first 'console.log' is being called at all? If I remove the call to the init function //nick.lowman.init() firebug still outputs 1. Window test.html, undefined. Why is that? Why does nick.lowman() get called by the window object when the html page loads?

Many thanks

+3  A: 

Also, I'm not entirely sure why the first 'console.log' is being called at all?

nick.lowman = function(){
    // ...
}();

Here you define a function, call it immediately (hence ()) and assign its return value ({init: init}) to nick.lowman

So the execution is:

  1. Create a variable called nick if there isn't one with a non-falsey value already
  2. Create an anonymous function that…
  3. Creates a variable called helloA in its own scope
  4. Outputs data using console.log containing "1" (as is), this (the window because the function is executing in the global context instead of as a method), and this.helloA (window.helloA, which doesn't exist.
  5. Defines a function called init
  6. Returns an object which gets assigned to nick.lowman
  7. Then you call nick.lowman.init() which executes the init function in the context of lowman.
  8. This defines helloB
  9. Then it console.logs with "2" (as is), this (lowman), and this.helloB (nick.lowman.helloB - which doesn't exist)

So the first output you get is from console.log('1.',this, this.helloA);

I think your main problem is that you are confusing this.foo (properties on the object on which a method is being called) with variable scope (variables available to a function)

David Dorward
Nick Lowman
I didn't say it defines `helloB` in the context of `lowman`. It defines in the **scope** of `lowman`. A variable existing in a given scope is not the same as a property on an object.
David Dorward
Thanks so much for your help David. I've spent the morning creating some examples and I think I'm slowly getting it.
Nick Lowman
+2  A: 
  1. this is defined on a per-function basis when the function call is made. When you call a function as o.f(), this will be o within the function, and when you call it as f(), this will be the global object (for browsers, this is the window). You wrote nick.lowman = function(){...}(); and the right-hand part is of the form f(), hence the Window.

  2. var foo = bar; defines a local variable. It may not be accessed as this.foo (well, except when you're at global scope, but that's silly). To define a member, you usually write this.foo = bar; instead.

Victor Nicollet
Victor, that was great answer thanks. Do you know why the window object called the function?
Nick Lowman
To be exact, when you call a function on its own (not as a member), `this` is the global object, which for web browsers is `Window`.
Victor Nicollet
Thanks again Victor. Much appreciated.
Nick Lowman
A: 

Ordinary vars in the constructor can be considered 'private' so your use of this.helloA & this.helloB isn't valid and prints undefined.

To expose them you would use this.helloA = 'Hello A'; instead of var helloA.

Alex K.
That would set them to be `window.helloA` not `nick.lowman.helloA`
David Dorward
Which means that my local variables are no longer private as I can access them using window.helloA. Is that right?
Nick Lowman
+2  A: 

It's much simpler if you think about this as a function, not as a variable. Essentially, this is a function which returns current "execution context", that is, the object the current function was "applied" to. For example, consider the following

function t() { console.log(this)}

this will return very different results depending upon how you call it

t() // print window

bar = { func: t }
bar.func() // print bar

foo = { x: 123 }
t.apply(foo) // print foo
stereofrog
Foo isn't be a function here so it won't have an apply method. I think you meant t.apply(foo). The apply method let's you define the execution context (value of this) explicitly as the function is invoked. (see also Function.call)The flexibility JavaScript has in this sense can be a very powerful tool but it is also a source of great frustration, particularly before you get your head around it!
adamnfish
Thanks for the reply stereofrog. Can I just ask about foo.apply(t)? Was that a mistake? Should it be t.apply(foo) //print foo?Many thanks
Nick Lowman
@Nick, err yes it's t.apply
stereofrog
Thanks stereofrog:)
Nick Lowman
+1  A: 

This is what your code does:

  • It creates an object and assigns to the variable nick.
  • It creates an anonymous function.
  • It calls the function (in the window scope).
  • It assigns the return value (an object containing the init property) to the lowman property of the object.
  • It gets the value from the init property, which is a method delegate, and calls the method.

The anonymous function does this:

  • It declares a local variable named helloA and assigns a string to it. (Creating a local variable doesn't add it as a property to the current object.)
  • It logs this (window) and the helloA property (which doesn't exist).
  • It creates an anonymous function and assignes to the local variable init.
  • It creates an object with the property init and the value from the local variable init.

The anonymous function assigned to the init property does this:

  • It declares a local variable named helloB and assigns a string to it. (Creating a local variable doesn't add it as a property to the current object.)
  • It logs this (the object from the lowman property, not the nick variable), and the helloB property (which doesn't exist).
Guffa
so should I use the this keyword when apply variables to an object i.e. this.helloA or does that make private varaibles become public?
Nick Lowman
@Nick: That depends on the current context. If the context is an object, you can use the `this` keyword to access the object. If it's the default context (window) you will be adding properties to the window object, thus making them global/public.
Guffa
Thanks for your help Guffa:)
Nick Lowman