正确使用变量的方式——在函数循环内部还是外部

3

我正在写一个循环时,突然遇到了一个问题。 “正确的方式”应该是什么:

    // code.. 
    $('tr','#my_table').each(function(){
        $(this).click(function(){
            var show_id = $(this).attr('data-show-id');
            // code..
            // code..
        });
    });
    // code.. 

或者

    // code.. 
    var show_id = 0; /* HERE */ 
    $('tr','#my_table').each(function(){
        $(this).click(function(){
            show_id = $(this).attr('data-show-id');
            // code..
            // code..
        });
    });

在第一个例子中,我为每个创建一个新的变量。在第二个例子中,我对表格中所有使用相同的全局,并在每个上重新填充它。
因此,问题是:无论编程语言如何 - 是在每次循环中创建一个新变量并在函数退出时释放,还是“捕获”整个运行的内存并避免每次重新创建变量更好的做法?

我相信你应该先定义它,然后在代码中稍后使用。 - Adam
4
不关乎你所问的内容,你根本不需要使用.each()。你可以写成$('tr','#my_table').click(function() { ... }); - nnnnnn
1
@nnnnnn已经知道了。我需要让我的例子更清晰。谢谢澄清。 - Phoenix
2
我认为我们不能真正忽略编程语言。例如,在C ++中,如果变量包含用户定义类的实例,其构造函数和/或析构函数运行成本很高,那么你肯定会想把它放在你能管理的最外层范围内。但是在JavaScript中,大多数时候不会有任何区别。 - Frédéric Hamidi
如果你要在多个事件处理程序之间共享数据,最好在外部创建变量。 - Stano
关于你的实际问题,我会在函数内部声明变量。不过最好看一下该函数中还有什么其他内容,因为我不喜欢在变量值设置之后仅使用一次的情况下声明变量——也许你甚至不需要这个变量... - nnnnnn
5个回答

11

变量不是在循环期间创建的。它仅在事件处理程序中需要,在该处理程序将单独执行。将变量(仅在内部需要时)移至外部会有三个问题:

  • 访问更高范围的变量会稍慢一些
  • 垃圾回收不会收集值(不是内存高效的)
  • 异步行为在事件处理程序中访问变量时可能会出现问题,但处理程序被多次调用

第二点也是您问题的答案:

每次循环创建一个新变量并在函数退出时释放,还是在整个运行期间捕获内存并避免每次重新创建变量更好?

浪费整个时间的内存几乎没有意义,创建功能作用域很快。将声明移到变量使用之外可以考虑为不良实践,将其移动到不同范围甚至可能引入错误。

如果您在具有大内存的机器上运行,并且创建/删除对象(对于所有处理程序保持恒定)很慢,则预先分配内存是有意义的。


1

这完全取决于您需要该变量存在的位置。JS将提升变量到当前作用域的顶部(其中它被声明)或创建一个(邪恶的)全局变量,如果未声明var。

如果您在循环中声明一个变量,实际上并不重要:

for (var i=0;i<10;i++)
{
    var j = 1*2;
}

被翻译成:

var i, j;
for (i=0;i<10;i++)
{
    j = 1*2;
}

以下是一些经验法则:

  • 如果你正在使用全局变量,那么你可能犯了一个错误
  • 如果你在循环中声明变量,那么你可能根本不需要那个变量
  • 如果你没有声明变量,请尝试使用以下脚本:

(function()
{
    'use strict';
    //your code here
}());

并进行调试,直到消除全局变量。

在你的例子中,你提供了在全局作用域(或者说是在$(document).ready(callback作用域内)声明show_id的选择,或者在click处理程序内部声明。没有什么可以阻止你在each回调函数中声明变量,虽然在这种情况下不会有太大的区别,但这是另外一回事。

在JS中,函数都有自己的作用域,你需要在需要它的作用域中声明变量。

话虽如此,我注意到你的性能标签,从效率的角度来看,你的代码几乎是最糟糕的。而不是遍历行并在各处绑定事件处理程序:委托事件:

$('#my_table').on('click','tr',function(e)
{
    var show_id = e.target.attr('data-show-id');
});

在这段代码中,show_id将在单击处理程序返回后被垃圾回收,如果你想保留show_id,例如检查下一行何时被单击:
$('#my_table').on('click','tr',(function (show_id)
{
    return function(e)
    {
        console.log('show_id was: ' + show_id);
        show_id = e.target.attr('data-show-id');
        console.log('and is now: ' + show_id);
    };
}()));
console.log(show_id);//undefined

变量在作用域内(包裹return function的函数是声明show_id的作用域),但其返回值引用该变量,因此它不会被垃圾回收。在返回的函数之外,我们处于更高的作用域,因此根本无法访问该变量。我们可以做的是暴露其值:
var someVar = {};
$('#my_table').on('click','tr',(function (show_id)
{
    return function(e)
    {
        show_id = e.target.attr('data-show-id');
        someVar.myTableClicks = show_id;
        console.log(someVar.myTableClicks);//value of show_id
    };
}()));
console.log(someVar.myTableClicks);//undefined
//after a couple of clicks
console.log(someVar.myTableClicks);//some value

现在,我们可以在任何可以访问someVar的地方访问show_id的值。
个人而言,我更喜欢保持需要某些变量可用的组代码:
var access = (function(timer)
{
    var speed = 100,
    begin = function(callback, interval)
    {
        speed = +(interval) || speed;
        timer = setInterval(callback, speed);
    },
    stop = function()
    {
        clearInterval(timer);
    };
    return {start: start, stop: stop};
}());

在这个例子中,startstop 函数都需要访问 timer 变量,但我不希望外部代码干扰 timer 的值,所以我只公开了那些函数。据我所知,回调参数函数可以包含对 timerspeed 的引用,但它们不会引用我试图屏蔽的 speedtimer 变量,因为回调函数将在另一个作用域中声明,因此它无法访问该作用域。即便如此,如果您想要绝对确定,您总是可以这样做:
var access = (function(timer)
{
    var speed = 100,
    begin = function(callback, interval)
    {
        speed = +(interval) || speed;
        timer = (function(timer, speed)
        {//masks timer & speed from the higher scope
            return setInterval(callback, speed);
        }('abc', 'def'));//assign dummy values
        timer = setInterval(callback, speed);
    },
    stop = function()
    {
        clearInterval(timer);
    };
    return {start: start, stop: stop};
}());

2
但在 OP 的例子中,它要么在函数中,要么不在。声明不会被提升出函数... - nnnnnn
@nnnnnn: 不是,但这就是为什么我说:到当前作用域的顶部。 - Elias Van Ootegem
1
是的。我的意思是你并没有真正涵盖OP提出的情况。 - nnnnnn
@nnnnnn:我已经添加了更多信息,更具体的信息,与OP的问题相关。既然他标记了问题“性能”,我还建议委派事件,而不是绑定单个处理程序。 - Elias Van Ootegem

0

取决于您的需求。

第一个例子:

$('tr','#my_table').each(function(){
        $(this).click(function(){
            var show_id = $(this).attr('data-show-id');
            // code..
        });
    });

变量是局部的,可以在绑定到.each或.click的匿名函数内使用。

第二个例子:

var show_id = 0; /* HERE */ 
    $('tr','#my_table').each(function(){
        $(this).click(function(){
            show_id = $(this).attr('data-show-id');
            // code..
            // code..
        });
    });

这里的变量是全局的,因此它的作用域在整个脚本中都有效!!!


0

个人而言,我会使用:

    $("tr","#my_table").each(function(){
        $(this).on("click", function(){
             var $this = $(this);                
             var show_id = $this.data("show-id");
            // code..
            // code..
        });
    });
  1. 使用on("click", function() {...});代替.click()
  2. 推荐使用var $this = $(this),以防您在代码中稍后需要使用它,否则您可以忽略它
  3. 在这种情况下,show_id将无法在此范围之外访问。
  4. 要访问data-*属性,应该使用data("-")而不是attr("-")

关于您提到的第4点,请记住,如果您使用.data()访问data-属性,jQuery将尝试猜测值的数据类型并进行转换,因此您可能得不到预期的结果。.attr()返回原始字符串。关于第1点,请您添加一些解释,说明为什么您建议这样做? - nnnnnn
还要查看有关 each 的问题的 @nnnnnn 评论。 - Bergi
我更喜欢使用on和off,因为当将事件绑定/解绑到对象时,我发现它更直观,更不用说.click将调用on("click", ...)。 - Valentin D

0

所有变量的内存将在方法入口处分配,因此这两个示例是等效的。第二个示例更好,因为由于变量的有限范围(不会意外地在循环外使用变量),它将更加安全。


这些示例等同,因为有3个方法入口。 - Bergi
所有变量的内存都将在方法入口处分配。您是说当外部函数开始时,JS环境会为内部函数中的变量分配内存吗?如果内部函数可能永远不会被调用,为什么要这样做呢?鉴于内部函数可能是递归的(不仅限于OP的代码),它又该如何执行呢? - nnnnnn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接