为什么javascript代码在循环内工作,一旦代码被提取到一个单独的函数

Why did javascript code inside loop work once the code was extracted to a separate function?

本文关键字:代码 一个 函数 单独 提取 循环 javascript 工作 为什么      更新时间:2023-09-26

我在jQuery插件中编写了以下代码,用于制作标记云。我传入一个格式为[{tag: "028", count: 15}, {tag: "101", count: 357}]的数据数组。我现在正在创建云作为跨度,根据计数规范化大小。span的创建是正确的,即它们具有正确的大小和文本。我添加了一些事件,点击和鼠标事件。无论单击哪个span,它总是显示数组中最后一个元素的警告。

在尝试调试正在发生的事情时,我将元素创建代码提取到一个单独的函数中。一旦我这样做了,点击事件正常工作,即点击事件显示了什么跨度被点击的正确数据。

我假设两个版本会产生相同的结果。为什么点击事件工作一旦我提取元素创建到它自己的函数?

这是不能工作的版本:

for (var i = 0; i < tagList.length; ++i) {
    if (tagList[i] != null) {
        var tagValue = tagList[i].tag;
        var tagCount = tagList[i].count;
        var size = getNormalizedSize(tagCount);
        var theSpan = getText(tagValue, tagCount);   // <span style="font-size: {1}em">{0}<'/span>
        var theAlert = getAlert(tagValue, tagCount); // "Project {0} is has logged in {1} drawings"
        var newElement = $(theSpan);
        newElement.click(function() {
            alert(theAlert);                        // Always shows data from last element in array
        }).mouseenter(function(event) {
            $(this).css('backgroundColor', '#FFC');
        }).mouseleave(function() {
            $(this).css('backgroundColor', '#FFF');
        });
        this.append(newElement).append(" ");
    }
}

这是有效的版本:

for (var i = 0; i < tagList.length; ++i) {
    if (tagList[i] != null) {
        var tagValue = tagList[i].tag;
        var tagCount = tagList[i].count;
        var tagElem = buildElement(tagValue, tagCount);
        this.append(tagElem).append(" ");
    }
}
function buildElement(tagValue, tagCount) {
    var size = getNormalizedSize(tagCount);
    var theSpan = getText(tagValue, tagCount);   // <span style="font-size: {1}em">{0}<'/span>
    var theAlert = getAlert(tagValue, tagCount); // "Project {0} is has logged in {1} drawings"
    var newElement = $(theSpan);
    newElement.click(function() {
        alert(theAlert);
    }).mouseenter(function(event) {
        $(this).css('backgroundColor', '#FFC');
    }).mouseleave(function() {
        $(this).css('backgroundColor', '#FFF');
    });
    return newElement;
}

在每个处理程序中捕获相同的变量,其作用域为循环。然后,当调用处理程序时,它包含分配给它的最后一个值。当您将其移动到函数中时,该变量的作用域为该函数调用的实例,因此它对于循环的每次迭代都是不同的,并且在该迭代中具有分配的值。

这是因为两个解决方案有不同的作用域规则。在第一种情况下,没有函数调用,这意味着theAlert只定义一次,您只需更新click回调处理程序引用的引用。在第二种情况下,您通过调用buildElement来创建一个新的作用域,在这种情况下,这意味着theAlert是为列表中的每个标记定义的,并且仅在定义时更新,因此每个click闭包引用不同的变量。

这里的关键是闭包中的变量(例如click callback)在运行之前不会被解析。下面是一个简单的例子来说明这一点:
var name = 'John';
setTimeout(function(){ alert(name); }, 1000);
name = 'Joe';

因此,即使当引用name的闭包在值更改之前创建时,它实际上不是运行,直到稍后,因此将被警告的名称是"Joe"而不是"John"。