为什么要避免使用JavaScript中的递增("++")和递减(" - ")运算符?

art*_*ung 348 javascript syntax jslint prefix-operator postfix-operator

jslint工具的一个提示是:

++和 -
已知++(增量)和 - (减量)运算符通过鼓励过多的诡计来导致错误的代码.它们仅次于故障架构,可以实现病毒和其他安全威胁.有一个plusplus选项禁止使用这些运算符.

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

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

cdm*_*kay 398

我的观点是始终在一行中使用++和 - ,如:

i++;
array[i] = foo;
Run Code Online (Sandbox Code Playgroud)

代替

array[++i] = foo;
Run Code Online (Sandbox Code Playgroud)

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

  • 这似乎是问题的核心和我的困惑.单独使用,i ++; 很清楚.在基本表达式周围添加其他代码的那一刻,可读性和清晰度开始受到影响. (62认同)
  • 同意,保持简单.为人类而不是机器编写代码 (17认同)
  • 你没有说你是在写C还是C++.可以说你应该在C++中使用前缀增量运算符,以防`i`变量以后变成复合类,在这种情况下,你将不必要地创建一个临时对象.我发现postfix运算符在美学上更令人愉悦. (5认同)
  • 是的,这也是我做的.很难出错,更容易让其他人弄清楚你在做什么. (3认同)
  • 同意,但它不仅与JavaScript有关 (2认同)
  • 我更喜欢1行版本.它提醒堆栈操作.push是数组[++ i] = foo,pop是bar = array [i--].(但是对于基于0的数组,array [i ++] = foo和bar = array [ - i]看起来更好).另一件事,如果有人在i ++之间添加了额外的代码,我会讨厌它; 和array [i] = foo;. (2认同)

Jon*_*n B 252

我坦率地对这个建议感到困惑.我的一部分想知道它是否与javascript编码器缺乏经验(感知或实际)有关.

我可以看到一个人如何"劈开"某些示例代码可能会在++和 - 中犯下一个无辜的错误,但我不明白为什么有经验的专业人士会避免它们.

  • @artlung也许它更多地与"懒惰和不专心的经验不足的黑客"有关,可能会搞乱这个代码,我不想混淆他. (14认同)
  • 我完全不同意你的回答.在我看来,这不是"我有足够的经验来使用它吗?" - 但'更清楚与否?' 如果在一个for循环中,或者单独一个,那就是明确的 - 每个人都会明白这一点并没有伤害.问题来自于将`++`放在更复杂的表达式中,而++ x与x ++不同,从而导致不容易阅读的内容.克罗克福德的想法不是"我能做到吗?" 它是关于'我怎样才能避免错误?' (9认同)
  • 好点Jon B.那就是为什么它让我感到困扰.Crockford是一名职业程序员(数十年),几十年来熟悉多种语言,并且从一开始就基本上使用JavaScript.我认为他的建议不是来自"我是懒惰而且不专心的经验不足的黑客" - 这就是为什么我试图了解它的来源. (7认同)

Ecl*_*pse 69

在C中有一段历史,例如:

while (*a++ = *b++);
Run Code Online (Sandbox Code Playgroud)

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

而且总是有什么问题

++i = i++;
Run Code Online (Sandbox Code Playgroud)

要么

i = i++ + ++i;
Run Code Online (Sandbox Code Playgroud)

实际上.它是用某些语言定义的,而在其他语言中则无法保证会发生什么.

抛开这些例子,我认为除了用于++递增的for循环之外,还有其他任何惯用语.在某些情况下,您可以使用foreach循环或者检查不同条件的while循环.但是扭曲你的代码以避免使用递增是荒谬的.

  • i = i ++ + ++ i; 让我的眼睛受伤了一点 - :) (101认同)
  • 为了提高工作安全性,请删除上一个示例中的空格. (14认同)
  • 唉,(a ++; b ++; x = a = b)与(a ++ + ++ b)的值不同. (10认同)
  • @Marius:`x = a +++ b` - >`x =(a ++)+ b` - >`x = a + b; A ++`.令牌者贪婪. (8认同)
  • 我也是.即使它们不是全部相同的变量,也就是说我有x = a ++ + ++ b; - 这种组合立即使x,a和b更难以保持在我的脑海中.现在,如果每个都是在单独的行上单独的语句,如在-a ++中; ++ B; x = a + b; 一见钟情会更容易理解. (3认同)
  • 在尝试+++ b,a ++ + b和+ + + b之前,它不会让人感到困惑 (2认同)
  • i = i ++ + ++ i; //不要这样做 (2认同)

Nos*_*dna 46

如果您阅读JavaScript The Good Parts,您会看到Crockford在for循环中替换i ++ 是i + = 1(不是i = i + 1).这非常干净和可读,并且不太可能变成"棘手"的东西.

Crockford 在jsLint中禁止自动增量和自动减少选项.您可以选择是否遵循建议.

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

我从C的多年经验中学到,如果我继续使用它,我就不会得到缓冲区溢出(或数组索引超出界限).但是我发现如果我陷入了在同一个声明中做其他事情的"过于棘手"的做法,我确实会得到缓冲区溢出.

因此,对于我自己的规则,使用i ++作为for循环中的增量是很好的.

  • @Eric重温你五年前的评论:哦,[你是对的!](https://en.wikipedia.org/wiki/Heartbleed) (10认同)
  • 每个人都得到缓冲区溢出.甚至OpenSSH也有他们的开源和他们的多个修改版本 (6认同)

Pau*_*tte 27

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

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
Run Code Online (Sandbox Code Playgroud)

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

a = b = c = 1; a ++ ; b -- ; c; console.log('a:', a, 'b:', b, 'c:', c)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

var foo = 1, bar = 2, baz = 3, alpha = 4, beta = 5, delta = alpha
++beta; //delta is 4, alpha is 4, beta is 6
Run Code Online (Sandbox Code Playgroud)

preincrement/postincrement confusion可以产生非常难以诊断的错误.幸运的是,他们也完全没必要.有更好的方法可以将1添加到变量中.

参考

  • 但如果您了解操作员,他们就不会"意外".它就像没有使用`==`,因为你不理解`==`和`===`之间的区别. (13认同)
  • 你的“空白”例子有什么棘手的地方?我得到一个简单的输出“a: 2 b: 0 c: 1”。我在第一个示例(“赋值语句”)中也没有看到任何奇怪或意外的东西。 (2认同)

Eri*_*ric 17

请考虑以下代码

    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++;
Run Code Online (Sandbox Code Playgroud)

因为i ++被评估两次输出(来自vs2005调试器)

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int
Run Code Online (Sandbox Code Playgroud)

现在考虑以下代码:

    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;
Run Code Online (Sandbox Code Playgroud)

请注意,输出是相同的.现在你可能会认为++ i和i ++是相同的.他们不是

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int
Run Code Online (Sandbox Code Playgroud)

最后考虑这段代码

    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++;
Run Code Online (Sandbox Code Playgroud)

输出现在是:

    [0] 0   int
    [1] 1   int
    [2] 0   int
    [3] 3   int
    [4] 0   int
    [5] 5   int
Run Code Online (Sandbox Code Playgroud)

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

  • 我在第一个例子后停了下来.你说"考虑以下代码".不,没有人写那个代码.写这个有缺陷的人而不是语言构造的是白痴. (28认同)
  • 结论是"当你在同一行或同一条指令上有多个++符号时要注意".我不认为你读过它 (7认同)
  • 你已经证明两个不同的代码产生不同的输出,这证明它很糟糕? (4认同)
  • 谢谢你做了那个实验.特别是"看起来很简单"的代码,但很难让我的大脑很快就能抓住它.绝对属于"棘手"类别. (3认同)

Pau*_*ier 16

增量和减量运算符的"前"和"后"性质往​​往会使那些不熟悉它们的人感到困惑; 这是他们可能变得棘手的一种方式.

  • 同意; 但是,经验丰富的专业人士*应该*不要混淆. (29认同)
  • 问题不在于一个优秀的程序员是否可以通过逻辑"按照自己的方式"工作 - 良好的代码理想情况下不需要任何工作来理解.我认为McWafflestix的观点仅仅是你不应该编写代码,经过简单的检查可能会导致在两周或两年内添加错误.我知道我已经为我没有完全理解的例程添加代码感到内疚:) (6认同)
  • 我认为这类似于避免将所有内容设为静态或全局的指南。作为一种编程结构,它本身并没有什么问题,但无知的过度使用可能会导致糟糕的代码。 (2认同)
  • @Mike:是的,请注意我没有指定运算符对于代码的原始作者或后来的维护者来说是否会很棘手。一般来说,我们尝试假设原始编码人员不会使用他们不熟悉/不熟悉的结构(遗憾的是,这通常是一个错误的假设);然而,这种熟悉程度肯定不会延伸到维护者(并且,正如上面括号中所指出的,甚至可能不会延伸到原始作者)。 (2认同)

Sri*_*ram 16

在我看来,"明确总是比隐含更好".因为在某些时候,您可能会对此增量语句感到困惑y+ = x++ + ++y.一个好的程序员总是让他或她的代码更具可读性.

  • x++ 或 ++y 中没有任何隐含的内容。 (3认同)

Han*_*rbe 14

避免++或 - 的最重要的基本原理是运算符返回值并同时引起副作用,使得更难以推断代码.

为了效率,我更喜欢:

  • ++ i的不使用的返回值(没有临时)
  • 我++使用的返回值(无流水线失速)

我是Crockford先生的粉丝,但在这种情况下我不得不反对.++i将解析的文本减少25%,i+=1 并且可以说更清晰.


Stu*_*eld 13

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

  1. 过去在其他语言中使用它来打破数组的界限并导致所有的不良行为
  2. 更令人困惑和缺乏经验的JS开发人员并不确切知道它的作用.

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

其次,如果我们避免复杂的事情,问题肯定不是我们有这个设施,但问题是那里有开发人员声称做JavaScript而不知道这些操作符是如何工作的?这很简单.value ++,给我当前值,然后在表达式中加上一个值,++ value,在给它之前递增值.

如果你只记得上面的内容,像++ ++ ++ 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;
Run Code Online (Sandbox Code Playgroud)

我想你必须记住谁必须阅读代码,如果你有一个团队知道JS内部,那么你不必担心.如果没有,那么评论它,写不同,等等.做你需要做的.我不认为增量和减量本质上是坏的或产生错误,或者创建漏洞,可能根据您的受众而言可读性较低.

顺便说一下,我认为道格拉斯·克罗克福德无论如何都是一个传奇,但我认为他对一个不值得的运营商造成了很大的恐慌.

我活得被证明是错的......


Evg*_*vin 10

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

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
Run Code Online (Sandbox Code Playgroud)

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

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}
Run Code Online (Sandbox Code Playgroud)


use*_*421 8

我认为程序员应该能够胜任他们使用的语言; 清楚地使用它; 并使用它.我认为他们应该人为地削弱他们使用的语言.我是根据经验说的.我曾经在Cobol商店隔壁工作,他们没有使用ELSE,因为它太复杂了.Reductio ad absurdam.