JavaScript closure and the this object

2019-02-22 14:19发布

问题:

I thought I had a reasonable understanding of the this object in JavaScript. When dealing with objects, callbacks, and both events and handlers, I haven't had a problem with it since time immemorial. Now, however, all has changed.

I've fallen head over heels in love with JavaScript. Pure JS, that is, not jQuery, prototype.js, dojo... So naturally, I've taken to using closures. In some cases, though, this is catching me off guard here. Take this snippet for one:

function anyFunc(par)
{
    //console.log(par);
    console.log(this);
}

function makeClosure(func)
{
    return function(par)
    {
        return func(par);
    }
}
var close = makeClosure(anyFunc);
close('Foo');

var objWithClosure = {cls:makeClosure(anyFunc),prop:'foobar'};
objWithClosure.cls(objWithClosure.prop);

var scndObj = {prop:'Foobar2'};
scndObj.cls = makeClosure;
scndObj.cls = scndObj.cls(anyFunc);
scndObj.cls(scndObj.prop);

In all three cases, this logs as the window object. It's an easy fix, of course:

function makeClosure(func)
{
    return function(par)
    {
        return func.call(this,par);
    }
}

This fix works, I put it here to avoid people answering this, without explaining what I need to know: why is this behaving the way it does here?

ensures the caller is effectively the object that the closure belongs to. What I fail to understand is this: Sure enough, this points to the window object in the first case, but in other cases, it shouldn't. I tried logging this in the makeClosure function just before returning, and it did log the object itself, not the window object. But when the actual closure is used, this is back to pointing to the window object. Why?

The only thing I can think of is that, by passing the anyFunc function as an argument, I'm actually passing window.anyFunc. So I tried this quick fix:

function makeClosure(func)
{
    var theFunc = func;
    return function(par)
    {
        theFunc(par);
    }
}

With the expected results, this now points to the objects, but again: Why? I have a few idea's (theFunc is a reference to the function in the local scope [this > private: theFunc]?), but I'm sure there are people here with a lot more know-how when it comes to JS, so I was hoping to get some more explanation or links to articles worth reading from them...

Thanks

Update

Here's a fiddle, may be I left something out, but here this logs all sorts of things ;)

Edit/Update 2

The case that confuses me is here.

Final Edit

Ok, This is getting a rather messy post. So to clarify: What I was expecting was a behaviour similar to this:

function makeClosure()
{
    function fromThisFunc()
    {
        console.log(this);
    }
    return fromThisFunc;
}

var windowContext = makeClosure();
windowContext();
var objectContext = {cls:makeClosure()};
objectContext.cls();

What caught me, was that the function anyFunc wasn't declared within the correct scope, and therefore, this pointed to the window object. I found this out by reading an ancient scroll I found somewhere on the web.

But something a little more complicated has happened because the function object now referred to by globalVar was created with a [[scope]] property referring to a scope chain containing the Activation/Variable object belonging to the execution context in which it was created (and the global object). Now the Activation/Variable object cannot be garbage collected either as the execution of the function object referred to by globalVar will need to add the whole scope chain from its [[scope]] property to the scope of the execution context created for each call to it.

So what I needed to do, was simplify rather then complicate things:

function fromThisFunc()
{
    console.log(this);
}

function makeClosure(funcRef)
{
    //some code here
    return funcRef;
}

That should work, right?

PS: I'll except Alnitak's answer, but special thanks goes to Felix Kling for all the patience and info.

回答1:

As soon as you call:

return func(par);

You're creating a new scope (with its own this) and in this case because you haven't specified an object, this === window usually or undefined in strict mode. The called function does not inherit whatever this was in the calling scope.

Ways to set a value for this are:

myobj.func(par);  // this === myobj

or

func.call(myobj, ...)  // this === myobj

There are also:

  • apply
  • bind
  • arrow functions, where this is set to the same value as the outer execution context (also see MDN:Arrow functions )


回答2:

The value of this depends only on whether you call the function as a method or as a function.

If you call it as a method, this will be the object that the method belongs to:

obj.myFunction();

If you call it as a function, this will be the window object:

myFunction();

Note that even if you are in a method that belongs to an object, you still have to call other methods in the object using the method syntax, otherwise they will be called as functions:

this.myOtherFunction();

If you put a method reference in a variable, you will detach it from the object, and it will be called as a function:

var f = obj.myFunction;
f();

The call and apply methods are used to call a function as a method even if it's not a method in the object (or if it's a method in a different object):

myFunction.call(obj);