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.
我知道像
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; |
事实上是这样。它是用某些语言定义的,而另一种语言则无法保证会发生什么。
撇开这些例子不谈,我认为没有比使用
如果你阅读了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循环可以用++,但是当你在同一行或同一条指令上有多个++符号时要小心。
在我看来,"显式总是比隐式好",因为在某种程度上,您可能会与这个递增语句
递增和递减运算符的"前置"和"后置"性质对于不熟悉它们的人来说往往是混淆的;这是它们可能很棘手的一种方式。
我一直在看道格拉斯·克罗克福德关于这个的视频,他对不使用递增和递减的解释是
首先,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++(无管道暂停)
我是克罗克福德先生的粉丝,但在这种情况下,我不得不不同意。
另一个例子比其他一些例子更简单,只返回递增值:
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更难阅读。--
我不知道这是否是他的推理的一部分,但如果你使用一个写得不好的缩小程序,它可以把
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的意图。