JavaScript 闭包和 this 对象

5
我原以为自己对JavaScript中的this对象有一个合理的理解。在处理对象、回调函数、事件和处理程序时,自古以来我都没有遇到过问题。然而,现在一切都改变了。
我已经深深地爱上了JavaScript,特别是纯JS,而不是jQuery、prototype.js、dojo等库。因此,我开始使用闭包。但在某些情况下,this却让我措手不及。例如,看看这段代码:
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);

在所有三种情况下,this都作为窗口对象记录。当然,这是一个容易解决的问题:
function makeClosure(func)
{
    return function(par)
    {
        return func.call(this,par);
    }
}

这个修复方法有效,我在这里放置它是为了避免人们回答这个问题,而不解释我需要知道的原因:为什么它在这里表现出这种行为?
确保调用者有效地是闭包所属的对象。我不明白的是这个: 当然,在第一种情况下,this指向窗口对象,但在其他情况下,它不应该。我尝试在返回之前在makeClosure函数中记录this,它确实记录了对象本身,而不是window对象。但是当实际闭包被使用时,this又会指向窗口对象。为什么?
我唯一能想到的是,通过将anyFunc函数作为参数传递,我实际上是传递了window.anyFunc。所以我尝试了这个快速修复:
function makeClosure(func)
{
    var theFunc = func;
    return function(par)
    {
        theFunc(par);
    }
}

有了预期的结果,this现在指向对象,但是为什么呢?我有一些想法(theFunc是本地作用域中函数的引用[this > private: theFunc]?),但我相信这里有很多在JS方面更有专业知识的人,所以我希望能从他们那里得到更多解释或值得阅读的文章链接...

谢谢

更新

这里有一个fiddle, 可能我漏掉了一些东西,但这里记录了各种各样的东西;)

编辑/更新2

让我困惑的情况在这里。

最终编辑

好的,这篇文章变得有点混乱了。所以澄清一下:我期望的行为类似于这个:

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

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

我注意到的是,函数anyFunc没有在正确的作用域内声明,因此this指向了窗口对象。我通过阅读在网上找到的古代卷轴发现了这一点。

但是因为全局变量现在引用的函数对象具有[[scope]]属性,该属性引用包含执行上下文中的激活/变量对象(和全局对象)的作用域链。现在,激活/变量对象也无法进行垃圾回收,因为由globalVar引用的函数对象的执行将需要将整个作用域链从其[[scope]]属性添加到为每次调用它创建的执行上下文的作用域中。

所以我需要做的是简化而不是复杂化事情:
function fromThisFunc()
{
    console.log(this);
}

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

这应该可以正常工作,对吧?

附注:我会接受Alnitak的答案,但特别感谢Felix Kling的耐心和信息。


我真的不知道除了提供一个关于闭包的全局解释链接之外,还能做出什么其他回答。这门课程是一篇优秀的阅读材料,应该能消除所有的疑虑:http://ejohn.org/apps/learn/ - Denys Séguret
问题不在于我不能理解闭包。造成困扰的是,我并不确定当我像这样创建闭包时this对象会发生什么。 - Elias Van Ootegem
2
我无法重现您所描述的行为。我得到了 DOMWindowObjectObject:http://jsfiddle.net/GErkX/,而您的第二个“修复”给了我 3 个 DOMWindow:http://jsfiddle.net/tZXQF/。 - Felix Kling
哪种是正确的行为方式,所以如果它像你描述的那样工作,我会非常惊讶。 - Felix Kling
关于你的第二次编辑:这里有什么让人困惑的?你像调用普通函数一样调用了func(par),因此在函数内部,this指向window。这就是你期望的第一个情况。 - Felix Kling
显示剩余2条评论
2个回答

4

一旦您打电话:

return func(par);

你正在创建一个新的作用域(具有自己的this),在这种情况下,因为你没有指定对象,this === window通常或在严格模式下为未定义。被调用的函数不会继承调用作用域中的任何this
设置this值的方法包括:
myobj.func(par);  // this === myobj

或者

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

还有以下内容:


1
或者 -显然-在makeClosure函数中创建一个局部变量,并将func参数赋值给该变量...另外,return func(par)是一个被返回给对象的函数的函数体,所以我假设对func(par)的调用将在对象的上下文中进行。为什么不是呢? - Elias Van Ootegem
2
@EliasVanOotegem:我认为你的测试有些缺陷,特别是最后一个。无论函数定义在哪里,每当你进行简单的函数调用foo()时,this都将引用全局对象。你在哪个环境中测试代码?在这种情况下,创建本地变量并没有什么区别。参数也是本地的。你的第一个修复方法在这里可以正常工作:http://jsfiddle.net/GErkX/ - Felix Kling
@FelixKling:我正在Chrome中进行测试,并提供了一个包含确切(混乱)代码的fiddle。第一个修复确实有效,但我想知道为什么this有时是我需要的对象,而其他时候是window对象。 - Elias Van Ootegem
@EliasVanOotegem:所以我对你的问题描述错了吗?听起来好像你用.call()修复后仍然得到了3个window对象: “我在makeClosure函数中尝试记录这个,就在返回之前,它确实记录了对象本身,而不是window对象。但是当实际闭包被使用时,它又指向了window对象。” 这并不是事实。它记录了对象。既然你期望如此,那么你似乎理解了一切是如何工作的。或者没有?我强烈建议阅读https://developer.mozilla.org/en/JavaScript/Reference/Operators/this。 - Felix Kling
@RobG 我不同意你的一些编辑 - .bind 只是 .call 的一个包装器,箭头函数明确地 不会 设置 this - 它只是将其保留在外部作用域中的值。 - Alnitak
显示剩余3条评论

1

this 的值仅取决于您将函数作为方法还是作为函数调用。

如果您将其作为方法调用,则 this 将是该方法所属的对象:

obj.myFunction();

如果您将其作为函数调用,则this将是window对象:
myFunction();

请注意,即使您在属于对象的方法中,仍然必须使用方法语法调用对象中的其他方法,否则它们将被视为函数:
this.myOtherFunction();

如果您将方法引用放入变量中,则会将其与对象分离,并将其作为函数调用:
var f = obj.myFunction;
f();

callapply方法用于调用函数作为方法,即使它不是对象中的方法(或者它是另一个对象中的方法):

myFunction.call(obj);

谢谢,但这并不是对这个问题的回答...我目前使用call,但我想知道JS为什么会表现出这样的行为。关于方法引用部分:当我在附加到对象的函数中创建一个函数引用(而不是方法,一个函数),那么这个函数引用将被调用为方法,这很奇怪。 - Elias Van Ootegem
@EliasVanOotegem 这是因为一旦你将函数引用“附加”到一个对象上时,它就成为该对象的方法。这与“分离”方法的相反情况相同。 - Alnitak
但我并没有直接附加该函数,而是附加了makeClosure,并将一个函数引用传递给它。在makeClosure内部,该传递的引用被分配给一个变量。当我使用该变量时,作为参数传递的函数会被调用为方法,但当我直接使用参数时,则会作为函数被调用...这就是令人困惑的地方。 - Elias Van Ootegem
1
@EliasVanOotegem:不,它没有:http://jsfiddle.net/tZXQF/ 如果您指的是http://jsfiddle.net/3chdp/,请注意您仍在使用`.call()`。 它与局部变量无关,如果您将其删除,则会得到相同的结果。 - Felix Kling
@FelixKling:你是对的,抱歉...过度记录“this”让我有点困惑。 - Elias Van Ootegem
@EliasVanOotegem:不用担心 :) 那么...一切都清楚了吗?还是在这方面有什么你想知道的吗? - Felix Kling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接