关于语法:为什么要避免JavaScript中的递增(“++”)和递减(“ — ”)运算符?

Why avoid increment (“++”) and decrement (“--”) operators in JavaScript?

jslint工具的一个技巧是:

++ and --
The ++ (increment) and -- (decrement)
operators have been known to contribute to bad code by
encouraging excessive trickiness. They
are second only to faulty architecture
in enabling to viruses and other
security menaces. There is a plusplus
option that prohibits the use of these
operators.

我知道像$foo[$bar++]这样的php结构很容易产生一个错误,但是我找不到比while( a < 10 ) do { /* foo */ a++; }for (var i=0; i<10; i++) { /* foo */ }更好的方法来控制循环。

jslin是否强调了它们,因为有一些类似的语言缺少"EDOCX1"(3)和"EDOCX1"(4)语法或处理方式不同,或者是否有其他的理由来避免我可能缺少"EDOCX1"(3)和"EDOCX1"(4)?


我的观点是总是在一行中使用+和--单独使用,如:

1
2
i++;
array[i] = foo;

而不是

1
array[++i] = foo;

除此之外的任何事情都会让一些程序员感到困惑,在我看来,这是不值得的。for循环是一个例外,因为增量运算符的使用是惯用的,因此总是清晰的。


坦率地说,我对那个建议感到困惑。我的一部分人想知道它是否与缺乏使用JavaScript编码器的经验(感知或实际)有更多关系。

我可以看到有人只是"黑客"破解了一些示例代码,如何使用++和———犯了一个无辜的错误,但我不明白为什么一个有经验的专业人员会避免这些错误。


在C语言中有这样的历史:

1
while (*a++ = *b++);

为了复制一个字符串,也许这就是他所指的过度欺骗的根源。

总有一个问题是什么

1
++i = i++;

1
i = i++ + ++i;

事实上是这样。它是用某些语言定义的,而另一种语言则无法保证会发生什么。

撇开这些例子不谈,我认为没有比使用++递增的for循环更惯用的了。在某些情况下,您可以使用foreach循环,或者检查不同条件的while循环。但是扭曲代码以尝试避免使用递增是荒谬的。


如果你阅读了javascript的好部分,你会发现crockford在for循环中对i++的替换是i+=1(而不是i=i+1)。这是非常干净和可读的,不太可能变形成"棘手的事情"。

Crockford在JSlint中不允许自动递增和自动提取选项。你可以选择是否听从建议。

我个人的规则是不要做任何与自动增量或自动增量相结合的事情。

我从C的多年经验中了解到,如果我简单地使用它,就不会出现缓冲区溢出(或数组索引越界)。但我发现,如果我陷入在同一语句中执行其他操作的"非常棘手"的实践中,我确实会得到缓冲区溢出。

因此,对于我自己的规则,使用i++作为for循环的增量是可以的。


在循环中,它是无害的,但在赋值语句中,它可能导致意外的结果:

1
2
3
var x = 5;
var y = x++; // y is now 5 and x is 6
var z = ++x; // z is now 7 and x is 7

变量和运算符之间的空白也可能导致意外的结果:

1
a = b = c = 1; a ++ ; b -- ; c; console.log('a:', a, 'b:', b, 'c:', c)

在闭包中,意外结果也可能是一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
var foobar = function(i){var count = count || i; return function(){return count++;}}

baz = foobar(1);
baz(); //1
baz(); //2


var alphabeta = function(i){var count = count || i; return function(){return ++count;}}

omega = alphabeta(1);
omega(); //2
omega(); //3

它会在换行后触发自动分号插入:

1
2
var foo = 1, bar = 2, baz = 3, alpha = 4, beta = 5, delta = alpha
++beta; //delta is 4, alpha is 4, beta is 6

preincrement/postincrement confusion can produce off-by-one errors that are extremely difficult to diagnose. Fortunately, they are also complete unnecessary. There are better ways to add 1 to a variable.

工具书类

  • jslint帮助:递增和递减运算符


考虑以下代码

1
2
3
4
5
6
7
8
9
    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[i++] = i++;
    a[i++] = i++;
    a[i++] = i++;

因为I++的计算结果是(来自VS2005调试器)

1
2
3
4
5
    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

现在考虑以下代码:

1
2
3
4
5
6
7
8
9
    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = ++i;
    a[++i] = ++i;
    a[++i] = ++i;

注意,输出是相同的。现在你可能认为我和我是一样的。它们不是

1
2
3
4
5
    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

最后考虑这个代码

1
2
3
4
5
6
7
8
9
    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = i++;
    a[++i] = i++;
    a[++i] = i++;

现在输出为:

1
2
3
4
5
6
    [0] 0   int
    [1] 1   int
    [2] 0   int
    [3] 3   int
    [4] 0   int
    [5] 5   int

所以它们是不一样的,混合两者会导致不那么直观的行为。我认为for循环可以用++,但是当你在同一行或同一条指令上有多个++符号时要小心。


在我看来,"显式总是比隐式好",因为在某种程度上,您可能会与这个递增语句y+ = x++ + ++y混淆。一个好的程序员总是使他或她的代码更易读。


递增和递减运算符的"前置"和"后置"性质对于不熟悉它们的人来说往往是混淆的;这是它们可能很棘手的一种方式。


我一直在看道格拉斯·克罗克福德关于这个的视频,他对不使用递增和递减的解释是

  • 在过去,它在其他语言中被用来打破数组的界限,导致各种各样的不道德和
  • 更让人困惑的是,缺乏经验的JS开发人员不知道它到底做了什么。
  • 首先,javascript中的数组是动态大小的,因此,如果我错了,请原谅,不可能打破数组的边界,访问不应该在javascript中使用此方法访问的数据。

    第二,我们应该避免复杂的事情,当然问题不是我们有这个工具,而是有一些开发人员声称要做javascript,但不知道这些操作人员是如何工作的??很简单。value++,给我当前值,在表达式中添加一个值之后,++value,在给我之前增加该值。

    像a++++b这样的表达式,只要记住上面的内容,就可以很容易地计算出来。

    1
    2
    3
    4
    5
    var a = 1, b = 1, c;
    c = a ++ + ++ b;
    // c = 1 + 2 = 3;
    // a = 2 (equals two after the expression is finished);
    // b = 2;

    我想你只需要记住谁必须通读代码,如果你的团队对JS了如指掌,那么你就不必担心了。如果没有,那就评论它,用不同的方式写它,等等。做你要做的。我不认为增量和减量本质上是坏的,或者生成错误,或者创建漏洞,可能只是不太可读,这取决于你的读者。

    顺便说一句,我认为道格拉斯·克罗克福德无论如何都是一个传奇人物,但我认为他给一个不值得的接线员制造了很多恐慌。

    不过,我活得被证明是错的…


    避免使用++或--的最重要的理由是,运算符同时返回值并产生副作用,这使得对代码进行推理变得更加困难。

    为了提高效率,我更喜欢:

    • ++i不使用返回值时(无临时)
    • 使用返回值时使用i++(无管道暂停)

    我是克罗克福德先生的粉丝,但在这种情况下,我不得不不同意。++ii+=1少25%的文本需要解析,并且可以说更清晰。


    另一个例子比其他一些例子更简单,只返回递增值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function testIncrement1(x) {
        return x++;
    }

    function testIncrement2(x) {
        return ++x;
    }

    function testIncrement3(x) {
        return x += 1;
    }

    console.log(testIncrement1(0)); // 0
    console.log(testIncrement2(0)); // 1
    console.log(testIncrement3(0)); // 1

    如您所见,如果希望此运算符影响结果,则不应在RETURN语句中使用后递增/递减。但返回不会"捕获"后递增/递减运算符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function closureIncrementTest() {
        var x = 0;

        function postIncrementX() {
            return x++;
        }

        var y = postIncrementX();

        console.log(x); // 1
    }


    我认为程序员应该能够胜任他们正在使用的语言;清楚地使用它;并且很好地使用它。我认为他们不应该人为地破坏他们使用的语言。我是凭经验说话的。我曾经在隔壁的一家COBOL商店工作过,那里的人不使用其他的东西,因为那里太复杂了。简化和荒谬。


    我的观点是,在两种情况下应避免出现这种情况:

    1)当有一个变量在多行中使用,并且在使用它的第一个语句(或最后一个,或者更糟的是,在中间)上增加/减少它时:

    1
    2
    3
    4
    5
    // It's Java, but applies to Js too
    vi = list.get ( ++i );
    vi1 = list.get ( i + 1 )
    out.println ("Processing values:" + vi +"," + vi1 )
    if ( i < list.size () - 1 ) ...

    在这样的例子中,很容易忽略变量是自动递增/递减的,甚至是删除第一条语句。换句话说,只在非常短的块中使用它,或者只在几个CLOSE语句中变量出现在块中。

    2)如果是多个++和——关于同一语句中的同一个变量。很难记住在这种情况下会发生什么:

    1
    result = ( ++x - --x ) * x++;

    考试和专业测试询问类似上面的例子,实际上我在寻找其中一个例子的文档时偶然发现了这个问题,但在现实生活中,不应该强迫人们对一行代码进行如此多的思考。


    正如一些现有答案中所提到的(我无法对此发表评论),问题是x+++x对不同的值进行了计算(在增量之前和之后),这并不明显,如果使用该值,可能会非常混乱。cdmckay非常明智地建议允许使用递增运算符,但只能以不使用返回值的方式使用,例如在其自身行上使用。我还将在for循环中包含标准用法(但仅在第三条语句中使用,其返回值未使用)。我想不出另一个例子。我自己被"烧焦"了,我也会为其他语言推荐同样的指导方针。

    我不同意这种过于严格的说法,因为很多JS程序员缺乏经验。这正是"过于聪明"的程序员的典型写作方式,我相信在更传统的语言中,以及在具有此类语言背景的JS开发人员中,这一点更为常见。


    在我的经验中,++i或i++从来没有引起过混淆,除了第一次了解操作员的工作方式。它是最基本的循环,而循环是由任何高中或大学课程教的语言,你可以使用操作员。我个人发现做一些像下面这样的事情比用一个+单独的行更好地看和读。

    1
    2
    3
    while ( a < 10 ){
        array[a++] = val
    }

    最后,它是一种风格偏好,而不是其他,更重要的是,当您在代码中这样做时,您会保持一致,以便其他处理相同代码的人可以遵循,而不必以不同的方式处理相同的功能。

    而且,Crockford似乎使用i-=1,我发现它比i或i更难阅读。--


    我不知道这是否是他的推理的一部分,但如果你使用一个写得不好的缩小程序,它可以把x++ + y变成x+++y。但同样的,一个写得不好的工具会造成各种各样的破坏。


    Fortran是C语言吗?它既没有+,也没有--。以下是如何编写循环:

    1
    2
    3
    4
    5
    6
    7
    8
         integer i, n, sum

          sum = 0
          do 10 i = 1, n
             sum = sum + i
             write(*,*) 'i =', i
             write(*,*) 'sum =', sum
      10  continue

    索引元素i每次通过循环由语言规则递增。如果你想以1以外的值递增,例如倒数2,语法是…

    1
    2
    3
    4
    5
          integer i

          do 20 i = 10, 1, -2
             write(*,*) 'i =', i
      20  continue

    python是C-like吗?它使用范围和列表理解以及其他语法来绕过增加索引的需要:

    1
    2
    print range(10,1,-2) # prints [10,8.6.4.2]
    [x*x for x in range(1,10)] # returns [1,4,9,16 ... ]

    因此,基于对两种选择的初步探索,语言设计者可以通过预测用例并提供一种可选的语法来避免使用++和。

    Fortran和python是不是比程序语言更不容易吸引bug?我没有证据。

    我声称fortran和python是C-like,因为我从未见过一个精通C的人,他不能以90%的准确率正确地猜测非模糊的fortran或python的意图。