为什么要避免在JavaScript中使用递增("++")和递减("——")操作符?

其中一个jslint工具的提示是:

< p > __ABC0和-- < br / > ++(递增)和--(递减) 众所周知,操作符会导致糟糕的代码 鼓励过度狡诈。他们 仅次于有缺陷的架构 使病毒和其他 安全威胁。这是一个加分项 选项,禁止使用这些 运营商。< / p >

我知道像$foo[$bar++]这样的PHP结构可能很容易导致off-by-one错误,但我想不出比a更好的方法来控制循环:

while( a < 10 ) do { /* foo */ a++; }

for (var i=0; i<10; i++) { /* foo */ }

jslint是否突出显示它们,因为有一些类似的语言缺少“__abc1”;和“;--"语法或处理方式不同,或者有其他理由避免“;++"和“;--"我可能错过了什么?

364023 次浏览

对于不熟悉自增和自减操作符的人来说,它们的“前”和“后”性质容易让人感到困惑;这是它们可能很棘手的一种方式。

Fortran是一种类c语言吗?它既没有++也没有——。下面是如何编写循环:

     integer i, n, sum


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

索引元素在每次循环中都按照语言规则递增。如果你想增加除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是否更少吸引bug ?我没有证据。

我之所以说Fortran和Python是类似C语言的语言,是因为我从来没有遇到过一个精通C语言的人不能以90%的准确率正确地猜出非混淆的Fortran或Python的意图。

C语言中有这样的历史:

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

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

总是会有一个问题

++i = i++;

i = i++ + ++i;

实际上做的。它在一些语言中有定义,而在其他语言中并不能保证会发生什么。

除了这些例子,我认为没有什么比使用++进行递增的for循环更习惯的了。在某些情况下,您可以使用foreach循环或while循环来检查不同的条件。但是为了避免使用递增而扭曲代码是荒谬的。

我的观点是在一行中单独使用++和——,如下所示:

i++;
array[i] = foo;

而不是

array[++i] = foo;

在我看来,任何超出这个范围的事情都会让一些程序员感到困惑。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++;

,因为i++的值是输出的两倍 (from 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循环适用于++,但当你在同一行或同一指令上有多个++符号时要小心

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

Crockford在jsLint中禁止选项的自增和自减。你可以选择是否听从建议。

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

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

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

我一直在看Douglas Crockford关于这个的视频他对不使用递增和递减的解释是

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

首先,JavaScript中的数组是动态大小的,因此,如果我说错了,请原谅我,在JavaScript中使用这种方法不可能打破数组的界限并访问不应该访问的数据。

其次,我们是否应该避免那些复杂的东西,当然问题不在于我们有这个工具,而在于有些开发者声称要做JavaScript,但不知道这些操作符是如何工作的?这很简单。Value ++,给我当前的值,在表达式之后加1,++ Value,在给我它之前增加值。

像a ++ + ++ 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了如指掌的团队,那你就不用担心了。如果不是,那么评论它,以不同的方式写它,等等。做你该做的。我不认为递增和递减本身就不好,或者产生错误,或者产生漏洞,可能只是根据你的读者的不同,可读性更差。

顺便说一句,我认为道格拉斯·克罗克福德是一个传奇,但我认为他引起了很多恐慌,因为他是一个不值得的操作员。

我活着就是为了证明我是错的……

我认为程序员应该精通他们所使用的语言;清楚地使用它;好好利用它。我认为他们应该人为地削弱他们正在使用的语言。这是我的经验之谈。我曾经在一家Cobol商店的隔壁工作,他们不使用ELSE“因为它太复杂了”。荒谬还原。

正如在一些现有的答案中所提到的(令人恼火的是,我无法评论),问题是x++ ++x计算为不同的值(在增量之前vs之后),这并不明显,可能非常令人困惑- 如果该值被使用。Cdmckay非常明智地建议允许使用增量操作符,但只能在不使用返回值的情况下使用,例如在它自己的行上。我还将在for循环中包含标准用法(但仅在第三条语句中,其返回值未被使用)。我想不出其他的例子了。我自己也被“烧伤”了,我也会把同样的指导方针推荐给其他语言。

我不同意这种过分严格是因为很多JS程序员缺乏经验的说法。这正是典型的“过于聪明”的程序员所写的东西,我敢肯定,在更传统的语言中,以及在具有此类语言背景的JS开发人员中,这种情况更为常见。

因为在某些时候,你可能会对这个增量语句y+ = x++ + ++y感到困惑。一个好的程序员总是使他或她的代码更易于阅读。

避免使用++或——的最重要理由是,操作符返回值的同时会引起副作用,使代码更难推理。

为了效率考虑,我倾向于:

  • 当返回值为不使用时,返回值为+ +我(非临时)
  • 当返回值为使用时,返回值为我+ +(没有管道阻塞)

我是克罗克福德先生的粉丝,但在这件事上我不同意。++i要解析的文本比i+=1少25%。

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

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有更好的方法。

参考文献

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

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语句中使用后递增/递减操作。但是return不会“捕获”自增/自减操作符:

function closureIncrementTest() {
var x = 0;


function postIncrementX() {
return x++;
}


var y = postIncrementX();


console.log(x); // 1
}

根据我的经验,除了第一次学习操作符如何工作外,++i或i++从未引起过混乱。它对于最基本的for循环和while循环是必不可少的,任何高中或大学课程都是用可以使用运算符的语言教授的。我个人认为,做下面这样的事情比把++放在单独的一行上更好看。

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

最后,这只是一种风格偏好,而不是别的什么,更重要的是,当你在代码中这样做时,你可以保持一致,这样其他人就可以使用相同的代码,而不必以不同的方式处理相同的功能。

另外,Crockford似乎使用i-=1,我发现它比——i或i——更难读懂

我认为在两种情况下应该避免使用:

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

考试和专业测试会询问类似上述的例子,事实上,我在寻找其中一个的文档时偶然发现了这个问题,但在现实生活中,人们不应该被迫对一行代码考虑这么多。

在作为前缀和后缀使用时,操作符的含义不同,这可能会导致难以发现的错误。考虑下面的例子,使用bubbleSort:

function bubbleSort(array) {
if(array.length === 1) return array;


let end = array.length - 2;
do {
for (let i = 0; i < array.length; i += 1) {
if (array[i] > array[i + 1]) {
swap(array, i, i + 1);
}
}
} while (end--);
}


bubbleSort([6,5]);

让我们想象一下,在运行程序的过程中,我们将一个包含两项的值传递给排序函数。代码按原样运行良好:“do/while”;循环在达到条件之前首先执行。然而,程序认为end是错误的并在变量递减之前退出循环。

现在考虑下面的代码,其中--符号被用作前缀,而不是后缀。这段代码将进入一个无限循环:

function bubbleSort(array) {
if(array.length === 1) return array;


let end = array.length - 2;
do {
for (let i = 0; i < array.length; i += 1) {
if (array[i] > array[i + 1]) {
swap(array, i, i + 1);
}
}
} while (--end);
}


bubbleSort([6,5]);

现在,当我们达到while条件时,我们递减结束值之前检查它。返回-1,这在Javascript中是一个真值。

我对它们的使用方式没有强烈的意见,但我只是想说明,如果不小心使用它们,它们会导致真正的错误。

++的参数是,++不适用于会导致TypeError的字符串,这几乎总是可取的。