JavaScript运算符:递增和递减

发布于 大漠

JavaScript中的递增和递减运算符都是一元操作符,言外之意就是只能操作一个值的操作符。递增和递减操作符直接借鉴自C语言,各有两种版本:前置型(递增++i,递减--i)和后置型(递增i++,递减i--)。

在JavaScript中,递增(递减)的前置和后置运算符对于初学者都非常容易混淆。我就属于这一类型,这次下定决心把这两者的使用和不同之处了解清楚。如果你和我一样,不仿一起来了解一二。

前置型递增(递减)

前置型递增也称之为前增量(pre-increment)运算符,它对操作数进行增量计算,并返回其计算后的值。这样或许不太好理解,换过一种方式来理解:前增量指的先计算,后赋值

如果文字不好理解,直接上示例,因为代码也是一种好的阐述方式。假设有一个变量i,其值为1。那么前置递增为++i

var i = 1;
console.log(i); // => 1
++i;
console.log(i); // => 2

这个示例,前置递增++i操作符把i的值变成了2(也就是1+1)。实际上,执行前置递增++i和执行下面的表达式效果相同:

var i = 1;
i = i + 1;
console.log(i); // => 2

简单点讲,前置型递增就是先自身计算,再赋值给变量:

var i = 1;
var a = ++i;  // var i = 1; i = i + 1; a = i;
console.log(a); // => 2
console.log(i); // => 2

其中var a = ++i,实际上做了下面这几个操作:

i = 1;
a = i + 1;
i = i + 1;
a = i;

上面的可能有点简单,来看一个复杂一点的:

var i = 1;
a = (++i) + (++i) + (++i);

最终a的值是多少?把上面的简化为:

(function () {
    var i = 1;

    var foo = function () {
        var j;
        j = i + 1;
        i = i + 1; // =>2
        return j; // => 2
    };
    
    var bar = function () {
        var m;
        m = i + 1; // => 3
        i = i + 1; // => 3
        return m; // => 3
    };

    var baz = function () {
        var n;
        n = i + 1; // => 4
        i = i + 1; // => 4
        return n; // => 4
    };

    var a = foo() + bar() + baz(); // 2 + 3 + 4 = 9
    return a; // 9
})();

最终a的值是9

前置递减--i和前置递增++i类似,不同的是先做减法(减1),再赋值。比如:

var i = 6;
a = --i;
console.log(a); // => 5
console.log(i); // => 5

代码中的--i相当于:

var i =  6;
i = i - 1;
a = i - 1;
a = i;

执行前置递增和递减时,变量的值都是在语句被求值以前改变的(返回它递增(减)之后的值)。在计算机科学领域中,这种情况通常被称之为副效应。来看个例子:

var age = 29;
var anotherAge = --age + 2;
console.log(age) // => 28 (age = age - 1)
console.log(anotherAge); // 30 (anotherAge = age - 1 + 2)

这个示列中变量anotherAge的初始值等于变量age的值前置递减(age = age - 1)再加上2。也就是前置--age先做减法操作,age的值变成了28,所以再加上2,其值就是30

另外在JavaScript运算符中,前置递增(++i)和递减(--i)与执行语句的优先级相同,因此整个语句会从左至右被求值。再来看一个示例:

var foo = 1;
var baz = 20;
var bar = ++foo + baz;
var baf = foo + baz;
console.log(foo);  // => 2
console.log(bar);  // => 22
console.log(baf);  // => 22

在这里,bar等于22是因为foo选加了1之后与baz相加。而baf也等于22是因为相应的加法操作使用了foo加上1之后的值。

需要注意的是,表达式++i并不总和i = i + 1完全一样,++运算符从不进行字符串连接操作,它总是会将操作数转换为数字并增1。如果i是字符串"1"++i的结果是数字2,而i+1是字符串"11"

var i = "1";
console.log(typeof(i)); // => string
console.log(++i); // => 2
console.log(typeof(++i)); // => number

现来看i+1;

var i = "1";
console.log(typeof(i)); // => string
console.log(i+1); // => "11"
console.log(typeof(i+1)); // => string

后置型递增(递减)

前面也说过了,递增(递减)分前置型和后置型。前置型是++i(--i),后置型不同的是,将++(或--)放置在变量的后面,即i++(或i--)。后置型递增(递减)又称之为后增量(post-increment)运算符。

后置型递增是和前置型递增不同之处是其先赋值,后递增。简单点说,后置型递增是先将自身的值赋值给变量,然后再自增1。来看个例子:

var a = i++时,其实际上做了下面这样的操作:

i = 1;
j = i;
i = i + 1;
a = j;

对于后置递增运算符,其自身并不会改变语句的结果,因为递增是这条语句的唯一操作。

var i = 1;
console.log(i); // => 1
console.log(i++); // => 1

上面的例子中i++并没有改变值,仍然是1。当然,当语句中还包含其他操作符时,上述区别就会非常明显了。如下面的示例:

var num1 = 2;
var num2 = 22;
var num3 = num1++ + num2; // var n; n = num1; num1 = num1 + 1; num3 = n + num2 = 24
var num4 = num1 + num2; // num1 + num2 = 25
console.log(num1); // => 3
console.log(num3); // => 24
console.log(num4); // => 25

来看一个复杂一点的示例,把前面示例中的前置递增,换成后置递增:

var i = 1;
a = (i++) + (i++) + (i++);

此时的a又是多少呢?按同样的方法,将上面的表达式(i++) + (i++) + (i++)拆解一下:

(function () {
    var i = 1;

    var foo = function () {
        var j;
        j = i; // => 1
        i = i + 1; // => 2
        return j; // => 1
    };

    var bar = function () {
        var m;
        m = i; // => 2
        i = i + 1; // => 3
        return m; // => 2
    };

    var baz = function () {
        var n;
        n = i; // =>3
        i = i + 1; // => 4
        return n;
    };

    var a = foo() + bar() + baz(); // 1 + 2 + 3 = 6

    return a; // 6
})()

a最后的值是6。不过将前置递增和后置递增混合在一起做运算时,事情将会变得更复杂一些,不过按上述的方法去拆解,就不会出错。来看个示例:

var i = 2;
a = (++i) + (i++) + (++i) + (i++);

上面代码中a的值又是多少呢?

(function () {
    var i = 2;

    var foo = function () {
        var j;
        j = i + 1; // => 3
        i = i + 1; // => 3
        return j; // => 3
    };

    var baz = function () {
        var m;
        m = i; // => 3
        i = i + 1; // => 4
        return m; // => 3
    }

    var bar = function () {
        var n;
        n = i + 1; // =>5
        i = i + 1; // => 5
        return n; // => 5
    }

    var boo = function () {
        var l;
        l = i; // => 5
        i = i + 1; // => 6
        return l; // => 5
    }

    var a = foo() + baz() + bar() + boo(); // 3 + 3 + 5 + 5

    return a; // => 16
})();

后置递减(i--)和后置递增是类似的,不同的是,其先赋值,后减1。简单点看个示例:

var i = 2;
var a = i--;
console.log(a); // => 2
console.log(i); // => 1

其中i--就是:

var i = 1;
var m = i;
i = i - 1;

前置递增(递减) VS. 后置递增(递减)

如果你看完了前面两部分内容,到这里的话你对前置递增(递减)和后置递增(递减)之间的区别已有一定的了解。这部分再啰嗦一下两者之间的区别。

  • 前置递增:前置递增运算符++在变量的前面,如++i
  • 后置递增:后置递增运算符++在变量的后面,如i++
  • 前置递减:前置递减运算符--在变量的前面,如--i
  • 后置递减:后置递减运算符--在变量的后面,如i--

简单回顾两个示例,就拿前置递增和后置递增为例吧,先来看前置递的示例++i:

var i = 1;
console.log(i); // => 1

var a = ++i;
console.log(i); // => 2
console.log(a); // => 2

接下来看后置递增的示例:

var i = 1;
console.log(i); // => 1

var a = i++;
console.log(i); // => 2
console.log(a); // => 1

上面两示例可以看出:

  • 前置递增++ii先将身的值自增1(相当于i=i+1),再将自增后的值赋值给变量a
  • 后置递增i++i先将自身的值赋值给变量a,然后i再自增1(相当于i = i + 1
  • 前置递减--i, i先将自的值自减1(相当于i = i - 1),再将自减后的值赋值给变量a
  • 后置递减i--, i先将自身的值赋值给变量a,然后i再自减1(相当于i = i - 1)

其中原理并不复杂,主要是和JavaScript中运算符优先级有关系:

  • ++作为前置递增时,优先级为15--作为前置递减时,优先级为15
  • ++作为后置递增时,优先级为16, --作为后置递减时,优先级为16
  • =作为赋值运算符时,优先级为3

所以++(或 --)会优先于=而执行。

归纳起来:

前置递增(前置递减)运算符,先计算(自增1(或自减1)),再赋值,返回操作对象递增(递减)之后的值;后置递增(后置递减)运算符,先赋值,再计算(自增1(或自减1)),返回操作对象递增之前的值

可以理解成:

  • ++i,增加变量,返回一个新的值
  • i++,增加变量,返回旧的值
  • --i,减少变量,返回一个新的值
  • i--,减少变量,返回旧的值

看个示例:

var a = 5;
var b = 5;
c = ++a;
d = b++;
console.log(a); // => 6
console.log(b); // => 6
console.log(c); // => 6
console.log(d); // => 5

最后为了方便查阅,列表表格

JavaScript自增、自减运算符与表达式,假设i的初始值为6:

运算符 ++i --i i++ i--
名称 前置递增(前增量) 前置递减(前减量) 后置增量(后增量) 后置减量(后减量)
表达式 ++i --i i++ i--
描述 先自增1,后赋值 先自减1,后赋值 先赋值,后自增1 先赋值,后自减1
返回 返回操作对象递增的值 返回操作对象递减的值 返回操作对象递增之前的值 返回操作对象递减之前的值
示例 ++i; --i; i++; i--
i的结果 7 5 7 5

逻辑语句中的递增(递减)

for循环语句中,常会看到i++i--++i--i。那么像for语句中前置递增(递减)和后置递增(递减)有没有区别呢?

先来看看for循环中的i++++i

for(var i = 0; i < 10; i++) {
    console.log(i);
}
console.log("-------------- 华丽的分割线 --------------");
for(var i = 0; i < 10; ++i) {
    console.log(i);
}

运行结果如下图所示:

运算符

从运算的结果上来看,它们的结果是一致的。或许你会感觉有点不对,因为前面讲++ii++还是有区别的。那么仔细看看他们的运算顺序。

就上面的示例来说,for循环的执行顺序是:

  • 进入循环
  • 首先是初始化,var i = 0;
  • 条件判断i < 10;,如果条件满足,进入执行语句console.log(i)
  • 循环结束后,执行i++(或者++i)
  • 执行完i++后跳到第三步,依此3-4-5-3-4-5这样的循环,直到条件不满足为止

for循环语句块换成:

for(A;B;C) {D}

实际上程序执行的过程是:A-(B-D-C)-(B-D-C)-(B-D-C)...-B这样的一个过程。或许你感觉他们的结果本不相同的,但输出为什么都是一样呢?我们再来回忆一下i++++i

先来看:

i++;

其等价于下面的语句:

i = i;
i = i + 1;

结果i增加了1。再来看看:

++i;

其等价于下面的语句:

i = i + 1;
i = i;

从上面的代码来看,不管怎么执行,他们最终都自增了1。那么将其赋值在别的值上呢?如下:

a = i++; 

等价于

a = i;
i = i + 1;

b = ++i;

其相当于:

i = i + 1;
b = i;

也就是b = i + 1

如此一来,将前面的示例改动一下:

for (var j = 0, i = 0; j < 10; j) {
    j = i++;
    console.log("循环" + j + ":" + "i=" + i + ",j=" +j);
}

console.log("------------------- 华丽的分割线 -------------------------");

for (var j = 0, i = 0; j < 10; j) {
    j = ++i;
    console.log("循环" + j + ":" + "i=" + i + ",j=" +j);
}

运算符

上图可以说明一切了吧。除了for循环之外,在JavaScript中还有whileswitch等,感兴趣的同学可以跑个测试,看看其结果是否相同。

前置递增(递减)和后置递增(递减)适用场景

不管是前置递增(递减)和后置递增(递减)这四种操作符对任何值都适用,也就是它们不仅适用于整数,还适用于字符串、布尔值、浮点数值和对象。在应用于不同的值时,递增和递减操作符遵循下列规则:

  • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减1操作。字符串变量变成数值变量
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为NaN字符串变量变成数值变量
  • 在应用于布尔值false时,先将其转换为0再执行加减1的操作。布尔值变量变成数值变量
  • 在应用于布尔值true时,先将其转换为1再执行加减1的操作。布尔值变量变成数值变量
  • 在应用于浮点数值时,执行加减1的操作。
  • 在应用于对象时,先调用对象的valueOf()方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是NaN,则调用toString()方法后再应用前述规则。对象变量变成数值变量

来看个示例:

var s1 = "2",
    s2 = "z",
    b1 = false,
    b2 = true,
    f = 1.1,
    o = {
        valueOf: function() {
            return -1;
        }
    };

console.log(s1++); // => 3
console.log(s2++); // => NaN
console.log(b1++); // => 0
console.log(b2++); // => 1
console.log(f++);  // => 1.1
console.log(o++);  // => -1

其他三种结果,可以将上面的代码简单更改一下,然后跑一下测试,就能看到对应的结果。这里就不再做相应的测试了。

总结

这篇文章主要介绍了JavaScript中前置递增(递减)和后置递增(递减)的使用,以及它们之前的不同之处。对于初学者而言,++i(--i)和i++(i--)经常会混淆,不知道它们之间的具体区别。经过这次,对它们之间的不同之处有了一个基本的了解。最后,希望这篇文章对您有所帮助。