i++原理
业务场景是点击按钮3秒后自动跳转上一页,数字有倒计时效果
//成功
let interval = setInterval(()=>{
this.setData({ time:--this.data.time })
if(this.data.time == 0){
clearInterval(interval)
}
},1000)
//失败
let interval = setInterval(()=>{
this.setData({ time:this.data.time-- })
if(this.data.time == 0){
clearInterval(interval)
}
},1000)
简化至:
//成功,3,2,1,0
var time = 3
setInterval(() => {
time = --time
console.log('time', time);
}, 1000);
//失败,3,3,3,3
var time = 3
setInterval(() => {
time = time--
console.log('time', time);
}, 1000);
通过反编译得到字节码进行分析:
//i++
0 iload_0 //1.从栈中 局部变量表 获取3
//2.把3压入 操作数栈,此时操作数栈中有一个值,是3
1 iinc 0 by 1 //把局部变量表中索引为0的位置值,也就是3,进行+1,此时,局部变量表中的值变为4
4 ireturn //把在操作数栈的值或者是地址引用给出栈,弹出的是3
//++i
0 iinc 0 by 1 //把局部变量表中索引为0的位置值,也就是3,进行+1,此时,局部变量表中的值变为4
3 iload_0 //1.从栈中 局部变量表 获取4
//2.把4压入 操作数栈,此时操作数栈中有一个值,是4
4 ireturn //把在操作数栈的值或者是地址引用给出栈,弹出的是4
简而言之,i++是先返回值,再+1,
++i是先+1,再返回值
此时不禁产生疑问,++i成功改变了值,那么i=++i是不是赋值了两次,++i是不是赋值语句呢
此时,我们分析i = i++:
0 iconst_1 //常量池中常量 i=3 推送到操作数栈顶
1 istore_1 //将操作数栈顶 i=3 存储到局部变量表
2 iload_1 //1.从 局部变量表 获取3
//2.把3压入 操作数栈,此时操作数栈中有一个值,是3
3 iinc 1,1 //把局部变量表中索引为0的位置值,也就是3,进行+1,此时,局部变量表中的值变为4
//iload_1 //如果在此位置执行iload_1,才会把4压入操作数栈
6 istore_1 //将操作数栈顶 i=3 存储到局部变量表,(此时将局部变量中的4刷回了3!!!)
7 getstatic
10 iload_1 //1.从 局部变量表 获取3
//2.把3压入 操作数栈,此时操作数栈中有一个值,是3
11 invokevirtual
14 return //弹出3
同理,i = ++i:
0 iconst_1 //常量池中常量 i=3 推送到操作数栈顶
1 istore_1 //将操作数栈顶 i=3 存储到局部变量表
2 iinc 1,1 //把局部变量表中索引为0的位置值,也就是3,进行+1,此时,局部变量表中的值变为4
5 iload_1 //1.从 局部变量表 获取4
//2.把4压入 操作数栈,此时操作数栈中有一个值,是4
6 istore_1 //将操作数栈顶 i=4 存储到局部变量表
7 getstatic
10 iload_1 //1.从 局部变量表 获取4
//2.把4压入 操作数栈,此时操作数栈中有一个值,是4
11 invokevirtual
14 return //弹出4
此时我们发现,i=i++中,局部变量自增以后,将操作数栈中原来的数写入了局部变量表,局部变量表中的值刷了回去,再从局部变量表中读取值,写入操作数栈,弹出值,值当然没有产生变化
说明:
局部变量:故名只意,就是用来存储我们的基本类型局部变量的值,或者是存储我们的引用类型的地址引用
操作数栈:方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。
机器操作指令:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iinc