分析 HTTP 203 出的一个 JS 题目

6次阅读

共计 1867 个字符,预计需要花费 5 分钟才能阅读完成。

后续内容更新,请前往:个人博客,欢迎一起交流。
这是一道出自 HTTP 203 的 JS 题目。HTTP 203 是 Youtube 上的一个栏目,主要讲一些有趣的知识。
原题目是这样的:
for(
let i = (setTimeout(()=>console.log(i), 2333), 0);
i < 2;
i++
) {

}

// 问 2333 毫秒之后打印出什么
答案是 2333 毫秒后打印出 0。为什么呢?
在开始分析题目之前,我们先来回顾几个知识点:
for 语法
for (语句 1; 语句 2; 语句 3) {
被执行的代码块
}
语句 1(代码块)开始前执行;语句 2 定义运行循环(代码块)的条件;语句 3 在循环(代码块)已被执行之后执行;
执行的顺序为:1. 第一次循环,即初始化循环。首先执行语句 1(一般为初始化语句),再执行语句 2(一般为条件判断语句),判断语句 1 是否符合语句 2 的条件,如果符合,则执行代码块,否则,停止执行,最后执行语句 3。2. 其他循环:首先判断前一次语句 3 的执行结果是否符合执行语句 2 的条件,如果符合,继续执行代码块,否则停止执行,最后执行语句 3。如此往复,直到前一次语句 3 的执行结果不满足符合执行语句 2 的条件。
总的来说,执行顺序是一致的,先执行条件判断(语句 2),再执行代码块,最后执行语句 3。如此往复,区别在于条件判断的对象,在第一次判断时,是执行语句 1,初始化的对象,后续的判断对象是执行语句 3 的结果。
逗号表达式
逗号表达式,因为原题目中就有使用逗号表达式 let i = (setTimeout(()=>console.log(i), 2333), 0);。
逗号表达式的一般形式是:表达式 1, 表达式 2, 表达式 3 …… 表达式 n。逗号表达式的求解过程是:先计算表达式 1 的值,再计算表达式 2 的值,…… 一直计算到表达式 n 的值。最后整个逗号表达式的值是表达式 n 的值。看下面几个例子:
x=8*2, x*4 // 整个表达式的值为 64,x 的值为 16

(x=8*2, x*4), x*2 // 整个表达式的值为 32,x 的值为 16

x=(z=5, 5*2) // 整个表达式为赋值表达式,它的值为 10,z 的值为 5,x 的值为 10

x=z=5, 5*2 // 整个表达式为逗号表达式,它的值为 10,x 和 z 的值都为 5
逗号表达式用的地方不太多,一般情况是在给循环变量赋初值时才用得到。所以程序中并不是所有的逗号都要看成逗号运算符,尤其是在函数调用时,各个参数是用逗号隔开的,这时逗号就不是逗号运算符。
基础知识回顾完毕,我们通过几个简单示例一步一步地逼近原题目:
示例一:基础知识 for 循环
for (var i = 0; i < 2; i++) {
console.log(i);
}

// 打印什么
这个无需多说,答案输出 0 1。
示例二:我们稍微改造下,将 log 放入 setTimeout 中
for (var i = 0; i < 2; i++) {
setTimeout(() => console.log(i));
}

// 打印什么
答案输出 2 2。分析下:上述代码中,变量 i 是 var 命令声明的,在全局范围内都有效,所以全局只有一个变量 i。每一次循环,变量 i 的值都会发生改变,而循环内被赋给 setTimeout 内部的 console.log(i),里面的 i 指向的就是全局的 i。也就是说,这里面所有的 i 指向的都是同一个 i,导致运行时输出的是最后一轮的 i 的值,也就是 2。
示例三:我再稍微改造下,将上述 var 改为 let。
for (let i = 0; i < 2; i++) {
setTimeout(() => console.log(i));
}

// 打印什么
答案输出 0 1。分析下:上述代码中,变量 i 是 let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量,所以最后输出的是 0 1。你可能会问,如果每一轮循环的变量 i 都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。
原题目
for(
let i = (setTimeout(()=>console.log(i), 2333), 0); // 语句 1
i < 2; // 语句 2
i++ // 语句 3
) {

}

// 问 2333 毫秒之后打印出什么
答案是 2333 毫秒后打印出 0。分析下:上述题目中,变量 i 是 let 声明的,当前的 i 只在本轮循环有效,后面的表达式是逗号表达式,取最后一个值,即 i = 0,settimeout 在语句 1,由于语句 1 只在第一次循环执行,因此 settimeout 的作用域是第一次迭代的作用域,且只执行一次。第一次迭代时 i = 0,所以答案是 2333 毫秒后打印出 0。

正文完
 0