【问题标题】:JavaScript closure and the this objectJavaScript 闭包和 this 对象
【发布时间】:2012-05-09 08:01:07
【问题描述】:

我以为我对 JavaScript 中的 this 对象有一个合理的理解。在处理对象、回调以及事件和处理程序时,自古以来我就没有遇到过问题。然而现在,一切都变了。

我已经完全爱上了 JavaScript。纯 JS,即不是 jQuery、prototype.js、dojo……所以自然而然地,我开始使用闭包。不过,在某些情况下,this 让我措手不及。把这个 sn-p 当作一个:

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 有更多的专业知识,所以我希望从他们那里得到更多的解释或链接到值得一读的文章……

谢谢

更新

Here's a fiddle,可能是我遗漏了一些东西,但这里记录了各种各样的东西;)

编辑/更新 2

The case that confuses me 在这里。

最终编辑

好的,这是一个相当混乱的帖子。所以澄清一下:我所期待的是与此类似的行为:

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

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

让我印象深刻的是函数anyFunc 没有在正确的范围内声明,因此this 指向了窗口对象。我通过阅读我在网上某处找到的ancient scroll 发现了这一点。

但是发生了一些更复杂的事情,因为现在由 globalVar 引用的函数对象是使用 [[scope]] 属性创建的,该属性引用了一个范围链,该范围链包含属于它所在的执行上下文的 Activation/Variable 对象创建(和全局对象)。现在 Activation/Variable 对象也不能被垃圾回收,因为 globalVar 引用的函数对象的执行需要将整个作用域链从其 [[scope]] 属性添加到为每次调用创建的执行上下文的作用域中它。

所以我需要做的是简化而不是复杂化:

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

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

应该可以吧?

PS:我会排除 Alnitak 的回答,但要特别感谢 Felix Kling 提供的所有耐心和信息。

【问题讨论】:

  • 我真的没有得到其他答案,除了可以提供一个指向全球闭包解释的链接。本课程是一本优秀的读物,应该消除所有不确定性:ejohn.org/apps/learn
  • 问题不在于我没有关闭。导致头痛的原因是我不完全确定this 对象在以我在这里的方式进行闭包时会发生什么
  • 我无法重现您描述的行为。我得到DOMWindowObjectObject:jsfiddle.net/GErkX,而你的第二个“修复”给了我 3 x DOMWindow:jsfiddle.net/tZXQF
  • 顺便说一句,这是正确的行为,所以如果它能像你描述的那样工作,我会感到非常惊讶。
  • 重新编辑:这里有什么令人困惑的地方?您正在像普通函数 (func(par)) 一样调用 func,因此函数内部 this 指的是 window。这是您的第一个案例,这似乎是您所期望的。

标签: javascript object closures this


【解决方案1】:

只要你打电话:

return func(par);

您正在创建一个新范围(具有自己的this),在这种情况下,因为您没有指定对象,通常是this === window 或在严格模式下未定义。被调用函数继承调用范围内的this

this设置值的方法有:

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

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

还有:

【讨论】:

  • 或 - 显然 - 在 makeClosure 函数中创建一个局部变量,并将 func 参数分配给该变量......另外,return func(par) 是返回的函数的函数体到一个对象,所以我假设对func(par) 的调用将在对象的上下文中进行。为什么不是?
  • @EliasVanOotegem 我仍在努力解决前一个案例...我无法在本地复制它。
  • @EliasVanOotegem:我认为你的测试在某种程度上存在缺陷,尤其是最后一个。每当您进行简单的函数调用foo() 时,this 将引用全局对象,无论函数在何处定义。您在哪个环境中测试代码?在这种情况下,创建局部变量并没有什么不同。该参数也是本地的。您的第一个修复在这里可以正常工作:jsfiddle.net/GErkX
  • @FelixKling 我也无法复制这种情况(在 Chrome 18 下)
  • @FelixKling:我正在 Chrome 中进行测试,并且我提供了一个准确的(混乱的)代码。第一个修复确实有效,但我想知道 为什么 this 是我需要的,而窗口对象是另一个
【解决方案2】:

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

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

obj.myFunction();

如果将其作为函数调用,this 将是 window 对象:

myFunction();

注意,即使你在一个属于某个对象的方法中,你仍然必须使用方法语法调用该对象中的其他方法,否则它们将被作为函数调用:

this.myOtherFunction();

如果你把一个方法引用放在一个变量中,你会将它从对象中分离出来,它会作为一个函数被调用:

var f = obj.myFunction;
f();

callapply 方法用于将函数作为方法调用,即使它不是对象中的方法(或者如果它是不同对象中的方法):

myFunction.call(obj);

【讨论】:

  • 谢谢,但不是这个问题的真正答案...我目前使用call,但我想知道 为什么 JS 的行为方式。关于方法引用位:很奇怪,当我在附加到对象的函数中创建函数引用(不是方法,函数)时,该函数引用将被称为方法
  • @EliasVanOotegem 这是因为只要您将该函数引用附加到一个对象,它就会成为该对象的一个​​方法。这与 detach 方法时发生的情况相反。
  • 但我没有直接附加该函数,我附加了makeClosure,并将函数引用传递给它。在makeClosure 中,传递的引用被分配给一个变量。当我使用该变量时,函数(作为参数传递)被称为方法,但是当我直接使用参数时,它被称为函数......这是令人困惑的部分
  • @EliasVanOotegem:不,它没有:jsfiddle.net/tZXQF 如果您指的是jsfiddle.net/3chdp,请注意您仍在使用.call()。它与局部变量无关,如果您删除它,您会得到相同的结果。
  • @FelixKling:你说得对,对不起......过度记录this
猜你喜欢
  • 1970-01-01
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 2023-03-12
  • 2013-08-10
相关资源
最近更新 更多