为什么要避免在JavaScript中增加(“++”)和减少(“ – ”)运算符?

jslint工具的技巧之一是:

++和 –
已知++(增量)和(减量)运算符通过鼓励过度的技巧来促成不良代码。 它们仅次于有缺陷的体系结构,从而使病毒和其他安全威胁成为可能。 有一个plusplus选项禁止使用这些操作符。

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

jslint是否突出显示它们是因为有一些类似的语言缺少“ ++ ”和“ -- ”语法或者以不同的方式处理它,或者是否有其他的理由来避免我可能会丢失的“ ++ ”和“ -- ” ?

我的观点是总是在一行中使用++和 – 本身,如下所示:

 i++; array[i] = foo; 

代替

 array[++i] = foo; 

除此之外的任何东西都可能会让一些程序员感到困惑,在我看来这并不值得。 For循环是一个例外,因为增量操作符的使用是惯用的,因此总是清除。

我很坦然地被这个建议搞糊涂了。 我的一部分想知道,如果它与JavaScript编码器缺乏经验(感知或实际)有关。 我可以看到某个人只是在一些示例代码中“黑客”,可能会用++和 – 做一个无辜的错误,但是我不明白为什么有经验的专业人员会避免这个错误。

C中有一个历史记录,例如:

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

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

总是有什么问题

 ++i = i++; 

要么

 i = i++ + ++i; 

其实呢。 它是用一些语言来定义的,而在其他语言中则不能保证会发生什么。

除了那些例子之外,我认为没有比使用++增量的for循环更有意思的了。 在某些情况下,您可以使用foreach循环,或者使用while循环来检查不同的条件。 但是,扭曲你的代码,以避免使用递增是荒谬的。

如果你阅读JavaScript The Good Parts,你会发现Crockford在for循环中替换i ++是i + = 1(不是i = i + 1)。 这是相当干净和可读的,不太可能变成“棘手”的东西。

Crockford在jsLint中不允许自动增量和自动减量选项 。 你选择是否遵循这个建议。

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

我从C多年的经验中学到,如果我继续使用它,我不会得到缓冲区溢出(或数组索引越界)。 但是我发现,如果我陷入在同样的陈述中做其他事情的“过于棘手”的做法,我确实会遇到缓冲超支。

所以,对于我自己的规则,使用i ++作为for循环中的增量是没有问题的。

考虑下面的代码

  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++; 

因为我++得到两次评估输出(从vs2005调试器)

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

现在考虑下面的代码:

  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和i ++是一样的。 他们不是

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

最后考虑这个代码

  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++; 

现在的输出是:

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

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

在我看来, “显性永远比隐性更好”。 因为在某些时候,您可能会对这个增量语句y+ = x++ + ++y感到困惑。 一个好的程序员总是让他或她的代码更具可读性。

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

 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 

变量和操作符之间的空格也会导致意外的结果:

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

在封闭中,意想不到的结果也是一个问题:

 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 

并且在换行符后触发自动分号插入:

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

预先增加/增加后的混淆会产生非常难以诊断的错误的错误。 幸运的是,他们也完全没有必要。 有更好的方法给变量加1。

参考

  • JSLint帮助:增加和减少运算符

递增和递减操作符的“前”和“后”性质往​​往会使不熟悉它们的人感到困惑; 这是一个棘手的方法。

我一直在观看道格拉斯·克罗克福德的视频,他的解释是不使用增量和减量

  1. 过去在其他语言中被用来打破数组的界限,造成各种不良的行为
  2. 这是更令人困惑和没有经验的JS开发人员不知道它到底是什么。

首先,JavaScript中的数组是动态调整大小的,所以,如果我错了,请原谅我,不可能破坏数组的边界并访问不应该在JavaScript中使用此方法访问的数据。

其次,我们应该避免那些复杂的事情,当然问题不是我们有这个设施,但问题是有开发者声称做JavaScript,但不知道这些操作员是如何工作的? 这很简单。 值++,给我的当前值和表达式后面加一个,++的值,在给我之前增加值。

像+++ ++ b这样的表达式,如果你只记得上面的内容,就很容易理解。

 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少了25%的文本可以说是更清楚。

我认为程序员应该能够胜任他们正在使用的语言; 清楚地使用它; 并使用它。 我认为他们应该人为地削弱他们正在使用的语言。 我从经验讲。 我曾经在隔壁的Cobol商店工作过,因为它太复杂了,所以他们不使用ELSE。 减肥和荒唐

另外一个例子,比简单的返回增量值更简单一些:

 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语句中不应该使用后增加/减少。 但是回报并不“捕捉”后期增加/减少的操作符:

 function closureIncrementTest() { var x = 0; function postIncrementX() { return x++; } var y = postIncrementX(); console.log(x); // 1 } 

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

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

我不同意这个过于严格是因为很多JS程序员缺乏经验。 这是典型的“过于聪明”的程序员的写作的确切类型,我敢肯定,在更传统的语言和有这样的语言背景的JS开发人员更常见。

根据我的经验,除了首次了解操作员的工作方式之外,++ i或i ++从来不会造成混淆。 对于任何高中或大学课程教授的最基本的循环和while循环来说,使用操作员的语言是必不可少的。 我个人发现像下面的东西看起来和阅读比在一个单独的行++的东西更好。

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

我的2cents是他们应该避免在两种情况下:

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

 // 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 ) ... 

在这样的例子中,你可以很容易地错过变量是自动递增/递减的,甚至删除第一个语句。 换句话说,只能在非常短的块中使用它,或者只在几个关闭的语句中出现在块中的变量。

2)如果有多个++和 – 在同一个语句中关于相同的变量。 记住这样的情况很难记住:

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

考试和专业测试要求提供类似上述的例子,事实上,在寻找关于其中之一的文档的时候,我偶然发现了这个问题,但是在现实生活中,不应该被迫对一行代码进行太多的思考。

Fortran是C语言吗? 它既没有++也没有 – 。 这里是你如何写一个循环 :

  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,语法是…

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

是Python C类? 它使用范围列表解析和其他语法来绕过增加索引的需要:

 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明显少于具有++和 – 的程序语言。 我没有证据。

我声称Fortran和Python是类C的,因为我从来没有遇到过一个C语言流利的人,他不能以90%的准确性猜出正确的非混淆的Fortran或Python的意图。

Interesting Posts