JavaScript:函数中的变量赋值

8
考虑以下两个块:

块A

obj = {
    a: 1,
    b: 2,
    c: 3,
    f: function() {
        alert(this.a);
        alert(this.b);
        alert(this.c);
    }
}

Block B

obj = {
    a: 1,
    b: 2,
    c: 3,
    f: function() {
        var a = this.a;
        var b = this.b;
        var c = this.c;
        alert(a);
        alert(b);
        alert(c);
    }
}

一个方法比另一个更正确/更有效吗? 当然,这只是一个简化的例子 - 在我的代码中有更多变量,我试图做的是通过重新分配当前函数范围内的变量来节省时间,而不需要在函数中每次键入this.varName。 它可以工作,但它是否正确?
编辑:仅澄清,这些变量将在函数中广泛使用。一般共识似乎是,在这种情况下,通过本地范围的重新分配是正确的方法。

3
你在那个函数里忘了写 var,所以你正在创建一些 全局 变量。不过,目前还不清楚你所担心的问题是什么。把一个值复制到本地变量中并没有什么 问题,所以就这个标准来看是“正确”的。 - Pointy
12个回答

7

视情况而定。如果您只打算使用该值一次,那么增加存储和检索该值的开销是没有意义的。但是,如果在函数范围内多次引用该值,则最好仅获取一次。


为什么要存储多个相同的数据?在大多数情况下,var a = this.a; 对代码没有用处,并且可能会产生意想不到的后果:a++ 只会增加 a 变量,而不是 this.a,但如果 a 是一个对象,则 a.foo ='bar' 将设置两个值,因为对象是按引用传递的。避免混淆,直接在需要的地方使用变量即可。 - zzzzBov
3
我认为这个问题的重点不在于将不同作用域的变量赋值的做法,而在于是否要赋值变量的本质。 - Jeremy Holovacs

4

f: function() {
    a = this.a;
    b = this.b;
    c = this.c;
    alert(a);
    alert(b);
    alert(c);
}

全局变量的赋值和查找效率低下,而且通过 a = this.a 给全局的 a 赋值会污染全局命名空间。

编辑:

假设this.athis.b会导致访问器(getter)触发,而alert(a)会调用atoString方法。

那么这两种方式的操作顺序是有区别的。

var a = this.a, b = this.b;
alert(a); alert(b);

此代码执行了 (get a、get b、a toString 和 b toString) 操作。

alert(this.a); alert(this.b);

这段代码执行的是 (获取a并转换为字符串, 获取b并转换为字符串) 的操作。

可能有合理的原因选择一种操作顺序而不是另一种,但从效率上来看,第二种操作顺序可能更好。

由于操作顺序的差异,当仅使用一个成员时,您不应依赖保留语义的JavaScript压缩器将第一种操作优化为第二种操作。


据我理解,您的意思是将 a = this.a; b = this.b; c = this.c; 改为 var a = this.a; var b = this.b; var c = this.c;,是这样吗? - Mak
如果我将 var a = this.a 分配给函数的本地变量,这种情况会如何改变? - Alex
@Maxim,是的,这将修复全局泄漏。您仍然需要执行不必要的操作,将变量提取到本地。如果您希望在任何toStringvalueOf调用将值转换为字符串之前发生所有可能的getters评估,那么这很好,但否则,将值提取到本地变量只会增加代码大小并可能减慢解释速度。 - Mike Samuel

3

这要看具体情况。如果你在函数内只访问这个属性一次,第一种方法更快。但如果你需要多次访问它,使用修改后的第二个代码块会更快。

将第二个版本改为在 f() 中声明 abc 作为局部变量,可以避免多次扫描作用域链和遍历 this - 这仅适用于需要多次访问这些属性的情况。


3

1

你的例子中缺少一些关键要素。我只会关注 f 函数,假设其余代码相同:

如果你只是访问对象上存储的值,那么没有必要存储临时变量,这只会使代码变得混乱:

function () {
  //use the values as they are
  alert( this.a );
  alert( this.b );
  alert( this.c );
}

然而,如果你正在进行计算并需要暂时缓存结果以便重复使用,你应该使用本地变量。确保它们不会污染全局作用域(window对象);使用var使变量仅在本地持久存在。

function () {
  var foo;
  foo = this.a / this.b + this.c;

  alert( this.a * foo );
  alert( this.b / foo );
  alert( this.c + foo );
}

编辑添加:

有两种不同类型的变量被引用。附加到对象的变量(可以使用this.varnamethis ['varname']访问),以及仅存在于局部范围内的变量(使用var varname声明并使用varname访问)。

任何附加到对象的变量都可以公开访问,并且应该用于在函数调用之间公开数据或保留数据。在函数内部声明的任何变量仅在函数上下文中可访问,因此对函数而言是私有的。它们不会跨调用保留值,但是可以用于在调用子函数时保留数据。

块A块B之间,块A是与对象数据交互的首选方法,但在大多数情况下,函数执行更大的一系列操作,通常涉及更复杂的行为。如果函数包含需要this.athis.bthis.c的值的回调,则需要使用别名传递数据,因为this将在上下文之间更改。

这不会像预期的那样警报 123

f:function ()
{
  $(foo).click(function g(){
    //`this` does not refer to the object `f` belongs to, but the element being clicked on
    //and therefor is not likely to work as expected
    alert( this.a );
    alert( this.b );
    alert( this.c );
  });
}

这个版本将会

f:function()
{
  var a,b,c;
  a = this.a;
  b = this.b;
  c = this.c;
  $(foo).click(function g(){
        alert( a );
        alert( b );
        alert( c );
  });
}

我设置的方式是,一些变量将是数字、字符串和布尔值,另一些变量将是对 jQuery 对象的引用。 - Alex
@Alex,我的建议是保持简单。除非必要,否则不要使用变量别名。 - zzzzBov

1
第一种方式相对更高效,因为在第二种方式中,您正在复制变量,因此所有变量都需要一个额外的语句,并且将占用更多的内存空间(代码和变量)。

在给定的确切代码中,你是正确的。然而,如果你访问该属性超过一次,你应该将其声明为本地变量。 - JAAulde

0
如果您可以稍微更改对象结构,请尝试将所需的属性放入容器(z)中,例如:
obj = {
    z: {
        a: 1,
        b: 2,
        c: 3
    },
    f: function() {
        var z = this.z;
        for(var prop in z) {
            alert(z[prop]);
        }
    }
}

那有什么用途? - Dunes
更少的打字,正如原帖中所提到的。这可以用更少的代码达到相同的结果。 - Silkster

0
var obj = {
    a: 1,
    b: 2,
    c: 3,
    f: function () {
        for (var property in this) {
            if (property != "f")
                alert(property + "=" + this[property]);
        }
    }
};
obj.f();

0

如果唐纳德·克努斯(Donald Knuth)说“我们应该忘记小效率,大约97%的时间:过早优化是万恶之源”,已经去世了,他会在坟墓里翻身。至于我自己,我开始感觉像this

注意了,男孩和女孩们。效率并不重要!它根本不重要!

让我们忘记像@Pheonix这样的彻头彻尾的疯狂行为——顺便说一下,它的拼写是“Phoenix”!——实际上担心局部变量占用的空间,这些变量在函数结束时就被回收了,真是太可笑了,而是专注于原始问题。考虑以下代码片段:

$(function() {
    var b;
    var d = { b : 1 }
    var n = 100000;

    var now = function() {
       return new Date().getTime();
    };
    var doTime = function(f) {
        var t = now();
        f();
        return now() - t;
    };

    var tm1 = doTime(function() {
       for (var i=0; i<n; i++) {
           b = 1;
       }
    });   

    var tm2 = doTime(function() {
       for (var i=0; i<n; i++) {
           b = d.b;
       }
    });
    $('body').empty().html("<table><tr><tr><td>Time without</td><td>"
      + tm1 + "ms</td><tr>"
      + "<tr><tr><td>Time with</td><td>"+ tm2 + "ms</td><tr>"
      + "<tr><tr><td>Total diff</td><td>"+ (tm2 - tm1) + "ms</td><tr>"
      + "<tr><tr><td>Avg. diff</td><td>"+ ((1000000.0 * (tm2 - tm1)) / n)
      + "ns</td><tr>"
    + "</table");
});

在我的可憐五年老筆記本電腦上,它說像這樣的物件引用需要90納秒。這意味著,你可以每秒執行1100萬次。你會做1100萬次嗎?如果不是,就不要浪費一秒鐘去擔心它。
眾所周知,優化正確代碼比改正已經優化的代碼更容易。好好寫代碼,然後對其進行基準測試。如果速度太慢,找到瓶頸(我保證不是未能將物件引用的結果存儲在本地變量中),然後消除它們。
現在,讓我們回到你的常規編程中。

嘿,我喜欢那句话中的优化和正确性。虽然你在前面加了“出名”,但我之前从未听过这种说法... >_> - Dunes
1
@Dunes -- 嗯,“著名”是因人而异的(我自己直到上周也从未听说过Rebecca Black),但是带引号的短语在谷歌上有69000个搜索结果。 - Michael Lorton
我并不是说它不出名。只是我应该认出来它。Rebecca Black——比一句话更出名。真希望我也能这么说。 - Dunes
@Dunes - 那 真的 是您想在墓碑上留下的东西吗?“五十年前唱了首烂歌,大约成名了十五分钟,进入康复中心后就再也没被人听说过”?(我认为)成为一位有用建议的匿名作者更好。 - Michael Lorton
我认为正确的代码就是优化过的代码;在它变得高效之前,它并不正确。 - Jeremy Holovacs
显示剩余8条评论

0

原样返回。不行。由于您没有使用var来声明变量,因此您正在在全局范围而不是局部范围中声明这些变量。

如果您反复访问一个变量,那么将其缓存到局部范围中肯定是值得的。这是因为函数和对象具有要搜索的作用域链,如果变量在直接作用域中不可用,则必须搜索其父函数的作用域,最后才能搜索最外层函数的作用域以查找i。如果在此范围内不存在i,则还必须搜索全局范围。

(function () {
    var i = 10;
    (function () {
        (function() {
            console.log(i); // i is 10
        })();
    })();
})();

在本地作用域中缓存变量的问题在于,有时代码可能会变得难以阅读,在几乎任何情况下,可读性都比效率更重要(计算机处理低效代码比人类阅读糟糕编写的代码要好得多)。

在函数顶部声明变量

如果你真的关心效率/正确性,那么请在函数开头声明所有变量。这样可以使JavaScript在创建函数的本地作用域时非常高效。函数内部深处的声明是低效的,因为JavaScript必须首先检查本地作用域中是否有可用空间,并在需要时调整作用域大小。请注意,即使您没有在函数顶部声明变量,某些编译器也会为您执行此操作,因为它是ECMA规范的一部分--这可能会导致一些问题)。source

例如:

var i = 1;
function test() {
  console.log(i); // undefined
  var i = 10;
}
test();

本质上,JavaScript 引擎已将您的代码编译为:

var i = 1;
function test() {
    var i;
    console.log(i); // undefined
    i = 10;
}
test();

如果您需要对a、b或c进行进一步操作,则使用固定版本的代码。

var obj = {
    a: 1,
    b: 2,
    c: 3,
    f: function() {
        var a, b, c;
        a = this.a;
        b = this.b;
        c = this.c;
        alert(a);
        alert(b);
        alert(c);
    }
}

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