1

我正在努力在 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”和“休眠样式”都成为所有由此创建的处理程序使用的值(例如,而不是每个处理程序都保留自己独特的一组样式)。这就是导致我在创建函数时使用 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()”语句,我将非常感激!

谢谢!

4

2 回答 2

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 并忽略了对其 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事件!

于 2013-02-11T07:18:38.363 回答
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;
}
于 2013-02-11T07:16:47.690 回答