Есть ли способ предотвратить изменение «this» при переносе функции? - PullRequest
1 голос
/ 30 декабря 2008

Я хочу, чтобы все кнопки выполняли действие до и после обычного события щелчка. Поэтому мне пришла в голову «блестящая» идея перебрать все эти элементы и создать функцию-обертку.

Похоже, это работало довольно хорошо, когда я тестировал его, но когда я интегрировал его в наше приложение, он развалился. Я проследил это до значения «this», которое было изменено моей оболочкой. Пример кода иллюстрирует это; перед переносом обработчиков событий каждая кнопка отображает идентификатор кнопки при нажатии, но после ее переноса отображаемое имя в этом примере отображается как «undefined», или «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>

Ответы [ 3 ]

4 голосов
/ 30 декабря 2008

Вы можете использовать метод call для разрешения привязки, например, originalEventHandler.call(btn);

В качестве альтернативы может помочь библиотека, подобная прототипу - ее метод bind позволяет вам создать новую функцию, привязанную к указанному объекту, так что вы бы объявили originalEventHandler как var originalEventHandler = btn.onclick.bind(btn);

Наконец, для хорошей справки по вопросам связывания, см. Также Выход из ситуаций связывания в JavaScript

3 голосов
/ 31 декабря 2008

Как и Пол Диксон сказал, что вы можете использовать call , но я предлагаю вам использовать применить вместо.

Однако причина, по которой я отвечаю, заключается в том, что я обнаружил тревожную ошибку: Вы фактически заменяете все свои обработчики событий на обработчик событий последней кнопки. Не думаю, что это было то, что вы предполагалось, не так ли? (Подсказка: вы заменяете значение для 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 , когда исходный обработчик называется.

Внутри анонимной функции мне нужно получить ссылку на функцию, чтобы можно было получить значение свойств original и source . Поскольку у анонимной функции нет имени, я использую arguments.callee (которая поддерживается с MSIE5.5), чтобы получить эту ссылку и сохранить ее в переменной src.

Затем я использую метод apply , чтобы выполнить оригинальный обработчик onclick. apply принимает два параметра: первый будет значением this , а второй - массив аргументов. this должен быть элементом, к которому был прикреплен оригинальный обработчик onclick, и это значение было сохранено в source . arguments является внутренним свойством всех функций и содержит все аргументы, с которыми была вызвана функция (обратите внимание, что для анонимной функции не заданы какие-либо параметры, но если она все равно вызывается с некоторыми параметрами, они будут находится в свойстве arguments ).

Причина, по которой я использую apply , заключается в том, что я могу переслать все аргументы, с которыми была вызвана анонимная функция, и это делает эту функцию прозрачной и кросс-браузерной. (Microsoft помещает событие в window.event, но другие браузеры предоставляют его в первом параметре вызова обработчика)

1 голос
/ 30 декабря 2008

Ваша проблема в том, как замыкания работают в JavaScript. Честно говоря, я бы рекомендовал использовать фреймворк. Любой из них должен сделать обработку событий намного приятнее, чем делать это вручную.

...