避免使用eval()动态构建事件处理程序

时间:2013-02-11 06:53:18

标签: javascript dynamic event-handling eval

我正在努力管理javascript中动态构建的事件处理程序。

在一些地方,我构建了需要处理特定事件(主要是鼠标悬停,鼠标滑动,点击)的表单或控件。

诀窍在于,在很多情况下,事件处理程序本身需要合并由构建表单或控件的函数生成或传递的数据。

因此,我一直在使用“eval()”来构建事件并合并适当的数据,这已经运作得很好。

问题是我一直在看/听到“你永远不应该使用eval()!”以及一些日益丑陋的实现,其中我动态构建的事件处理程序需要动态构建其他事件处理程序,而嵌套的evals则非常迟钝(温和地说)。

所以我在这里,询问是否有人可以向我展示更好的方式(仅限原生javascript,我没有实施任何第三方库!)。

这是一个粗略的例子来说明我在说什么:

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    var inp = document.createElement('input');
    inp.id = controlName;
    inp.type = type;
    inp.style.cssText = dormantStyle;
    eval("inp.onfocus = function() { this.style.cssText = '" + activeStyle + "'; }");
    eval("inp.onblur = function() { this.style.cssText = '" + dormantStyle + "'; }");
    eval("inp.onclick = function() { " + whenClicked + "; }");
    return inp;
}

这个功能显然可以让我轻松创建许多不同的INPUT标签,并指定一些独特的属性和事件动作,每个只需一个函数调用。再一次,这是一个非常简化的例子,只是为了演示我正在谈论的内容,在某些情况下我目前使用的项目,事件可以包含几十行,他们甚至可以根据传递进行动态ajax调用参数或其他动态生成的数据。在更极端的情况下,我构造表,其各个行/列/单元可能需要根据处理程序的动态生成内容或处理程序的处理程序来处理事件。

最初,我已经构建了如上所述的函数:

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    var inp = document.createElement('input');
    inp.id = controlName;
    inp.type = type;
    inp.style.cssText = dormantStyle;
    inp.onfocus = function() { this.style.cssText = activeStyle; };
    inp.onblur = function() { this.style.cssText = dormantStyle; };
    eval("inp.onclick = function() { " + whenClicked + "; }");
    return inp;
}

...但是我发现无论最后一个赋值是什么,“activeStyle”和“dormantStyle”都成为了这样创建的所有处理程序所使用的值(而不是每个都保留了自己独特的样式集,例)。这就是导致我在创建函数时使用eval()“锁定”变量值的原因,但这导致我陷入噩梦,如下所示:

(这是我正在处理的一个动态构建的事件处理程序的示例,它使用嵌套的eval()函数):

    eval("input.onkeyup = function() { " +
            "InputParse(this,'ucwords'); " +
            "var tId = '" + myName + This.nodeName + "SearchTable" + uidNo + "'; " +
            "var table = document.getElementById(tId); " +
            "if (this.value.length>2) { " +
                "var val = (this.value.indexOf(',') >=0 ) ? this.value.substr(0,this.value.indexOf(',')) : this.value; " +
                "var search = Global.LoadData('?fn=citySearch&limit=3&value=' + encodeURI(val)); " +
                "if (table) { " +
                    "while (table.rows.length>0) { table.deleteRow(0); } " +
                    "table.style.display='block'; " +
                "} else { " +
                    "table = document.createElement('table'); " +
                    "table.id = tId; " +
                    "ApplyStyleString('" + baseStyle + ";position=absolute;top=20px;left=0px;display=block;border=1px solid black;backgroundColor=rgba(224,224,224,0.90);zIndex=1000;',table); " +
                    "var div = document.getElementById('" + divName + "'); " +
                    "if (div) { div.appendChild(table); } " +
                "} " +
                "if (search.rowCount()>0) { " +
                    "for (var i=0; i<search.rowCount(); i++) { " +
                        "var tr = document.createElement('tr'); " +
                        "tr.id = 'SearchRow' + i + '" + uidNo + "'; " +
                        "tr.onmouseover = function() { ApplyStyleString('cursor=pointer;color=yellow;backgroundColor=rgba(40,40,40,0.90);',this); }; " +
                        "tr.onmouseout = function() { ApplyStyleString('cursor=default;color=black;backgroundColor=rgba(224,224,224,0.90);',this); }; " +
                        "eval(\"tr.onclick = function() { " +
                            "function set(id,value) { " +
                                "var o = document.getElementById(id); " +
                                "if (o && o.value) { o.value = value; } else { alert('Could not find ' + id); } " +
                            "} " +
                            "set('" + myName + This.nodeName + "CityId" + uidNo + "','\" + search.id(i)+ \"'); " +
                            "set('" + myName + This.nodeName + "ProvId" + uidNo + "','\" + search.provId(i)+ \"'); " +
                            "set('" + myName + This.nodeName + "CountryId" + uidNo + "','\" + search.countryId(i) + \"'); " +
                            "set('" + input.id + "','\" + search.name(i)+ \"'); " +
                            "}\"); " +
                        "var td = document.createElement('td'); " +
                        "var re = new RegExp('('+val+')', 'gi'); " +
                        "td.innerHTML = search.name(i).replace(re,'<span style=\"font-weight:bold;\">$1</span>') + ', ' + search.provinceName(i) + ', ' + search.countryName(i); " +
                        "tr.appendChild(td); " +
                        "table.appendChild(tr); " +
                    "} " +
                "} else { " +
                    "var tr = document.createElement('tr'); " +
                    "var td = document.createElement('td'); " +
                    "td.innerHTML = 'No matches found...';" +
                    "tr.appendChild(td); " +
                    "table.appendChild(tr); " +
                "} " +
            "} else { " +
                "if (table) table.style.display = 'none'; " +
            "} " +
        "} ");

目前,我在使用嵌套eval()将“.onclick”事件绑定到表行时遇到问题,正如您所看到的,找出代码变得非常毛茸茸(调试也是如此,因为所有已知的原因)...所以,如果有人能够指出我能够实现这些目标同时避免可怕地使用“eval()”声明,我真的很感激!

谢谢!

2 个答案:

答案 0 :(得分:1)

除了许多其他原因之外,这也是从不使用eval 的原因。 (如果那些你正在“烘焙”的值包含引号怎么办呢?哎呀。)更一般地说,试着弄清楚为什么正确的方法不起作用而不是以错误的方式提交。 :)

同样,分配到on*属性不是一个好主意;它们不能特别好地扩展。新的热门是使用element.addEventListener,它允许多个处理程序用于同一事件。 (对于较旧的IE,你需要attachEvent。这种IE废话是我们开始首先使用像jQuery这样的库的主要原因。)


您粘贴的代码(使用闭包)应该可以正常工作。你没有包含的部分是你必须在循环中这样做。

JavaScript变量是函数作用域,而不是块作用域,因此当您执行此操作时:

var callbacks = [];
for (var i = 0; i < 10; i++) {
    callbacks.push(function() { alert(i) });
}

for (var index in callbacks) {
    callbacks[index]();
}

...你会得到9十次。循环的每次运行都会创建一个关闭相同变量i的函数,然后在下一次迭代中,i的值会发生变化。

您想要的是工厂功能:内联或独立。

for (var i = 0; i < 10; i++) {
    (function(i) {
        callbacks.push(function() { alert(i) });
    })(i);
}

这会创建一个单独的函数并立即执行。 i 里面的函数每次都是一个不同的变量(因为它的作用域是函数),所以这有效地捕获了外部 {{1并忽略对它的任何进一步更改。

你可以明确地解决这个问题:

i

完全相同的事情,但是功能是独立定义的,而不是内联的。

这有come up before,但要发现导致意外的原因有点棘手。


即使您的“正确方式”代码仍然使用字符串作为函数或样式的内容。我会将该点击行为作为一个函数传递,我会使用类而不是在我的JavaScript中嵌入CSS块。 (我怀疑我会为每一个输入添加一个ID。)

所以我写这样的东西:

function make_function(i) {
    return function() { alert(i) };
}

// ...

for (var i = 0; i < 10; i++) {
    callbacks.push(make_function(i));
}

这仍然存在一些问题:它在旧的IE中不起作用,它将删除您稍后尝试添加的任何类名。这就是jQuery很受欢迎的原因:

function create_input(id, type, active_class, onclick) {
    var inp = document.createElement('input');
    inp.id = id;
    inp.type = type;
    inp.addEventListener('focus', function() {
        this.className = active_class;
    });
    inp.addEventListener('blur', function() {
        this.className = '';
    });
    inp.addEventListener('click', onclick);

    return inp;
}

// Called as:
var textbox = create_input('unique-id', 'text', 'focused', function() { alert("hi!") });

当然,即使大部分内容都是不必要的 - 您也可以使用function create_input(id, type, active_class, onclick) { var inp = $('<input>', { id: id, type: type }); inp.on('focus', function() { $(this).addClass(active_class); }); inp.on('blur', function() { $(this).removeClass(active_class); }); inp.on('click', onclick); return inp; } CSS选择器,而不必担心:focusfocus事件!

答案 1 :(得分:0)

您不需要eval来“锁定”某个值。

从发布的代码中不清楚为什么在CreateInput返回后您看到值发生了变化。如果CreateInput实现了循环,那么我希望使用分配给activeStyledormantStyle的最后一个值。但即使从循环中调用CreateInput也不会导致您描述的错误行为,与评论者相反。

无论如何,这种陈旧数据的解决方案是使用闭包。 JavaScript局部变量都绑定到函数调用范围,无论它们是在函数内部还是在循环中声明。因此,您添加一个函数调用以强制创建新变量。

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    while ( something ) {
        activeStyle += "blah"; // modify local vars
        function ( activeStyle, dormantStyle ) { // make copies of local vars
            var inp = document.createElement('input');
            inp.id = controlName;
            inp.type = type;
            inp.style.cssText = dormantStyle;
            inp.onfocus = function() { this.style.cssText = activeStyle; };
            inp.onblur = function() { this.style.cssText = dormantStyle; };
            inp.onclick = whenClicked;
        }( activeStyle, dormantStyle ); // specify values for copies
    }
    return inp;
}