避免使用eval()动态生成事件处理程序

Avoiding use of eval() to dynamically build event handlers

本文关键字:动态 事件处理 程序 eval      更新时间:2023-09-26

我很难用javascript管理动态构建的事件处理程序。

在一些地方,我构建了需要处理特定事件(主要是鼠标悬停、鼠标退出、单击)的窗体或控件。

诀窍在于,在大量情况下,事件处理程序本身需要合并由构建窗体或控件的函数生成或传递到该函数中的数据。

因此,我一直在使用"eval()"来构建事件并合并适当的数据,这在一定程度上起到了很好的作用。

问题是,我经常看到/听到诸如"你永远不应该使用eval()!"之类的事情,以及一些越来越丑陋的实现,其中我的动态构建的事件处理程序需要动态构建其他事件处理程序,而嵌套的eval非常迟钝(委婉地说)。

因此,我在这里询问是否有人可以向我展示更好的方法(请仅限本地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()"语句,我将不胜感激!

谢谢!

除其他原因外,这就是为什么您不应该使用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每次都是不同的变量(因为它的作用域是函数),因此这有效地捕获了外部的i的值,并忽略了对它的任何进一步更改。

你可以明确地打破这一点:

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

完全相同,但函数是独立定义的,而不是内联的。

这种情况以前也出现过,但要发现是什么引起了惊喜有点棘手。


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

所以我会写这样的东西:

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!") });

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

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;
}

当然,即使其中大部分都是不必要的——您可以只使用:focusCSS选择器,而不必为focusblur事件而烦恼!

您不需要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;
}