【问题标题】:Is there anyway to prevent 'this' from changing, when I wrap a function?当我包装一个函数时,有没有办法防止“这个”发生变化?
【发布时间】:2010-09-28 21:29:05
【问题描述】:

我希望所有按钮在其正常的 onclick 事件之前和之后执行一个操作。所以我想出了一个“绝妙”的想法,循环遍历所有这些元素并创建一个包装函数。

当我对其进行测试时,它似乎工作得很好,但是当我将它集成到我们的应用程序中时,它就崩溃了。我将其追溯到包装器更改了“this”值。示例代码说明了这一点;在包装事件处理程序之前,每个按钮都会在您单击时显示按钮 ID,但在包装后显示的名称在此示例中为“未定义”,如果您从表单中运行它,则显示名称为“Form1”。

有人知道做同样事情的更好方法吗?还是保持最初预期的“this”值的好方法?

如您所想,我不想修改目标按钮中的任何现有事件处理程序代码。

提前致谢。

PS-目标浏览器为IE6及以上,不需要跨浏览器功能

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<script language="javascript" type="text/javascript">
    function btnWrap_onClick()
    {
        var btns = document.getElementsByTagName("button");
        for( var i = 0; i < btns.length; i++)
        {
            var btn = btns[i];

            // handle wrap button differerntly
            if( "btnWrap" == btn.id)
            {
                btn.disabled = true;
                continue; // skip this button
            }

            // wrap it
            var originalEventHandler = btn.onclick;
            btn.onclick = function()
            {
                alert("Starting event handler");
                originalEventHandler();
                alert("Finished event handler");
            }
        }

        alert("Buttons wrapped successfully");
    }
</script>
<body>
    <p>
    <button id="TestButton1" onclick="alert(this.id);">TestButton1</button>
    <button id="TestButton2" onclick="alert(this.id);">TestButton2</button>
    </p>
    <button id="btnWrap" onclick="btnWrap_onClick();">Wrap Event Handlers</button>
</body>
</html>

【问题讨论】:

    标签: javascript binding


    【解决方案1】:

    您的问题是闭包在 JavaScript 中的工作方式。老实说,我建议使用框架。他们中的任何一个都应该使事件处理比手动处理好得多。

    【讨论】:

      【解决方案2】:

      您可以使用call 方法来解析绑定,例如originalEventHandler.call(btn);

      另外,prototype 之类的库也可以提供帮助 - 它的 bind 方法允许您构建一个绑定到指定对象的新函数,因此您可以将 originalEventHandler 声明为 var originalEventHandler = btn.onclick.bind(btn);

      最后,有关绑定问题的良好背景资料,另请参阅Getting Out of Binding Situations in JavaScript

      【讨论】:

      • +1 非常感谢保罗的提示。只是为了让你知道,我给出了“一些”的答案,因为他预测并回答了我遇到的一些障碍。再次感谢。
      【解决方案3】:

      就像Paul Dixon 所说,你可以使用call,但我建议你改用apply

      但是,我回答的原因是我发现了一个令人不安的错误:您实际上是用最后一个按钮的事件处理程序替换所有事件处理程序。我不认为那是什么你是故意的,是吗? (提示:您在每次迭代中替换 originalEventHandler 的值)

      在下面的代码中,您可以找到一个有效的跨浏览器解决方案:

      function btnWrap_onClick()
      {
          var btns = document.getElementsByTagName("button");
          for( var i = 0; i < btns.length; i++)
          {
              var btn = btns[i];
      
              // handle wrap button differerntly
              if( "btnWrap" == btn.id)
              {
                  btn.disabled = true;
                  continue; // skip this button
              }
      
              // wrap it
      
              var newOnClick = function()
              {
                  alert("Starting event handler");
                  var src=arguments.callee;
                  src.original.apply(src.source,arguments);
                  alert("Finished event handler");
              }
              newOnClick.original = btn.onclick; // Save original onClick
              newOnClick.source = btn; // Save source for "this"
              btn.onclick = newOnClick; //Assign new handler
          }
      alert("Buttons wrapped successfully");
      }
      

      首先,我创建一个新的匿名函数并将其存储在变量 newOnClick 中。由于函数是一个对象,我可以像任何其他对象一样在函数对象上创建属性。我使用它来创建属性 original 是原始的 onclick 处理程序,以及 source 这是将成为 this 时的源元素调用原始处理程序。

      在匿名函数内部,我需要获取对该函数的引用才能获取属性 originalsource 的值。由于匿名函数没有名称,我使用 arguments.callee(自 MSIE5.5 起支持)获取该引用并将其存储在变量 src 中。

      然后我使用方法 apply 来执行原始的 onclick 处理程序。 apply 有两个参数:第一个是 this 的值,第二个是参数数组。 this 必须是原始 onclick 处理程序附加到的元素,并且该值保存在 source 中。 arguments 是所有函数的内部属性,并保存调用函数时使用的所有参数(请注意,匿名函数没有指定任何参数,但如果无论如何调用它时使用一些参数,它们将在 arguments 属性中找到)。

      我使用 apply 的原因是我可以转发调用匿名函数的所有参数,这使得该函数透明且跨浏览器。 (微软将事件放在 window.event 中,但其他浏览器在处理程序调用的第一个参数中提供它)

      【讨论】:

      • 现在当我再看一遍时,我意识到应该重构它以使用相同的匿名函数,并将旧调用者的值存储在原始对象上。那将使用更少的内存。
      • 谢谢,我确实注意到了这个错误,并且完全困惑于我将如何引用真正的原始事件处理程序和调用控制。你摇滚!
      • @John MacIntyre:谢谢约翰! (我只是好奇这是你的真名还是对 Trapper 的引用?也许两者兼而有之?)顺便说一句,如果你打算在生产中使用它,你应该考虑像我在上面的评论中所说的那样重构它。跨度>
      • 真实姓名。我确实使用了它,它将在下周晚些时候推出一个
      • @John:新年快乐!我休假一周,没有随身携带工具。上面的解决方案有效,它很干净,跨浏览器并且不会干扰其他任何事情(如果您不小心调用了两次,它甚至可以工作)。但是,它并不是最高效的内存,因为它创建了一个(续..)
      猜你喜欢
      • 2023-03-16
      • 2020-02-28
      • 1970-01-01
      • 1970-01-01
      • 2020-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多