当我包装一个函数时,无论如何都要防止'this'改变吗?

时间:2008-12-30 17:49:53

标签: javascript binding

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

当我测试它时,这看起来效果很好,但是当我将它集成到我们的应用程序中时,它就崩溃了。我追溯到'this'值被我的包装器改变了。示例代码说明了这一点;在包装事件处理程序之前,每个按钮在您单击时显示按钮ID,但在包装后,显示的名称在此示例中为“undefined”,如果从表单中运行,则为“Form1”。

有没有人知道做同样事情的更好方法?或者是保持最初预期的“这个”价值的好方法?

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

提前致谢。

PS-目标浏览器是IE6& up,不需要crossbrowser功能

<!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>

3 个答案:

答案 0 :(得分:4)

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

或者,类似原型的库可以提供帮助 - 它的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 中。由于函数是一个对象,我可以像任何其他对象一样在函数对象上创建属性。我使用它来创建属性原始,它是原始的onclick-handler,而 source 是源元素,它将是 this 调用原始处理程序。

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

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

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

答案 2 :(得分:1)

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