# [1]个人学习笔记,对基础知识的整理和巩固。
JS有几种循环语句:
- for
- for...in
- for...of(ES6 IE不支持)
- while
- do...while
- for each...in[2] (已废弃,不述)
- for await...in[3](异步,暂不述)
▉ while[4]
语法:
while ( condition) statement
条件为真时执行语句,如此往复,直到条件为假。
多行语句可以用大括号包裹。
▉ do...while[5]
语法:
do statement
while ( condition);
先执行一次,条件为真时重复执行,直到条件为假。
类似while,但至少执行一次。
▉ for[6]
最经典的for语句,语法:
for ([ initialization]; [ condition]; [ final-expression]) statement
先执行[initialization],然后在[condition]为真时,执行[statement]语句。
每次[statement]执行完毕后运行一次[final-expression],之后再次检测[condition],为真时再次执行[statement],以此往复,直到[condition]为假。
执行顺序示例:
var i=0;
for (console.log(1); console.log(2) || i < 1; console.log(4)||i++) {
console.log(3);
}
依次输出1、2、3、4 之后,输出2 ,然后结束(条件不符合,跳出)。
console.log()返回值为undefined,为假。
*第一次执行也会检查[condition]。
▉ for...of[7](ES6)
for ( variable of iterable) statement
for...of可用于迭代数组和其他任何可迭代对象[8](包括 Array,Map,Set,String,TypedArray,arguments 对象等等,也可以自定义可迭代对象,如jQuery对象(好像是自某个版本之后支持))。
基于ES6标准,IE不支持。
它非常好用。
let iterable = [1, 2, 3];
for (let value of iterable) {
console.log(value);
}
依次输出1、2、3。
如果你不想无意修改语句块中的变量 , 也可以使用const代替let。
用于迭代数组之外的其他可迭代对象的话,暂时不述(我还没研究到),可先参考MDN 。
▉ for...in[9]
for ( variable in object) statement
for...in用于遍历对象的(及其原型链上继承的)可枚举属性,variable为相应的属性名。
所谓可枚举属性可参照:属性的可枚举性和所有权 - MDN
如下例:
let obj={
a:1,
b:2,
__proto__:{ // __proto__为非正当用法 仅为此处简单示例
c:3
}
}
for (let name in obj) {
console.log(`${name}:${obj[name]}`);
}
将依次输出:a:1, b:2, c:3 。
MDN不建议将for...in用于遍历数组,可使用for循环或for...of以及forEach() 。
▉ break[10]
break [ label];
break语句用于中止当前循环、switch
语句、label
语句,并把程序控制流转到紧接着被中止语句后面的语句(MDN)。
可用于以上所述循环,switch语句,以及label标记的循环或语句块(label见下文)。
▉ continue[11]
continue [ label];
在for循环中,控制流跳转到更新语句;在while循环中,控制流跳转回条件判断。(MDN)
可用于以上所述循环,也可以搭配label使用(label见下文)。
▉ label[12]
label : statement
标签(label)用于标记一个循环或语句块,然后在break和continue语句中使用它。
如下例:
lable1:{
console.log(1); // 输出1
break lable1; // 跳出语句块
console.log(2); // 未运行 不输出
}
一个来自MDN的示例:
outer_block:{
inner_block:{
console.log ('1');
break outer_block; // breaks out of both inner_block and outer_block
console.log (':-('); // skipped
}
console.log ('2'); // skipped
}
上例只输出1 。
lable只在其标记的语句块或循环中有效。
用label来标记语句块时,只能搭配break使用,不能搭配continue。
continue label;
语句只适用于循环内。
用label来标记循环(来自MDN):
var i, j;
loop1:
for (i = 0; i < 3; i++) { //The first for statement is labeled "loop1"
loop2:
for (j = 0; j < 3; j++) { //The second for statement is labeled "loop2"
if (i == 1 && j == 1) {
break loop1; // 直接跳出外部循环
}
console.log("i = " + i + ", j = " + j);
}
}
// Output is:
// "i = 0, j = 0"
// "i = 0, j = 1"
// "i = 0, j = 2"
// "i = 1, j = 0"
// Notice the difference with the previous continue example
一般情况下,如果不搭配label,当循环嵌套时,内部循环体内使用break或continue只能跳出内部循环,而搭配使用label标签,可以在内部循环中跳出外部循环。
尽管如此,label仍然极少使用。
也可以用于while等循环。
#[1]
▉ let与for循环
首先是经典例子:
for (var i = 0; i < 3; i++) {
setTimeout(()=>{
console.log(i);
}
);
}
console.log(i); // 输出3
将输出3, 3, 3, 3 。
setTimeout的代码将在循环后依次执行,而var定义的变量i被后续的循环给更改了,所以全是3。
只要将var改为let:
for (let i = 0; i < 3; i++) {
setTimeout(()=>{
console.log(i);
}
);
}
将依次输出0, 1, 2 。
这其实挺让人疑惑,按照我们理解的for循环,理论上 let i=0 只执行了一次,所以应当只定义了一个i,为什么会有三个不同的值。
for循环似乎针对let作了特殊的处理。
首先来研究一下它的作用域:
{
let i;
for (let i = 0; i < 3; i++) {
let i;
}
}
上述代码不会报错,而let不能在同一作用域内重复声明,所以我们知道上例三处声明分别处于不同的作用域。
首先for处在一个无形的块作用域中,其代码块又是一个子块作用域,好像是这样:
{for (let i = 0; i < 3; i++) {
// 子块
}} // 似乎有一个无形的块
按照这种逻辑的话,小括号里的声明应该是在外层,为什么有三个不同的结果呢?
另外,在一次循环中对变量做出的改变,也仍然会影响到下一次循环,像这样:
for (let i = 0; i < 3; i++) {
console.log(i);
i++;
}
上例将输出 0, 2 。
经过测试和总结,let声明的变量似乎经历了中转,像这样:
for (let i = 0; i < 3; i++) {
setTimeout(()=>{
console.log(i);
}
)
}
// 就好像:
for (let i = 0; i < 3; i++) {
let _i = i;
{
let i = _i;
// ↓代码块
setTimeout(()=>{
console.log(i);
}
)
// ↑
_i = i;
}
i = _i; // 如果代码块中进行了改变,则此处进行
}
// 简便看:
for (let i = 0; i < 3; i++) {
// 处理1:对变量进行中转 创建一个仅在本次循环作用的变量i 并且等于原变量 i
// ↓代码块
// 本次循环中i是子变量i
setTimeout(()=>{
console.log(i);
}
)
// ↑
// 处理2:令原变量i的值等于子变量i 即,如果本次循环内i发生改变,则 原变量i 也将发生改变
}
看起来就是这样,这种理解暂时没有发现什么问题,很完美。
另外还有几点:
- 这种特殊处理只会针对在小括号里初始化语句中声明的变量:
let i;
for (i = 0; i < 3; i++) {
setTimeout(()=>{
console.log(i);
}
);
}
上例将输出3, 3, 3 。
- 如果声明多个变量,它们都会被进行特殊处理。
- 如果是数组或对象等等,仍然都是引用,所以可能都还是同一个,但是仍然可以认为经过了处理,因为引用也可能在循环中被改变。
- for...of和for...in之类的类同,只不过少了循环执行后的部分(处理2),因为不论怎么改变下一次循环都会重新取值,所以没有意义。
- const类同,但是不能改变。
最后的示例:
for (let a = 0, b = "", c = [], d; a < 3; a++) {
setTimeout(()=>{
console.log(a, b, c, d);
});
b += "1";
c.push(1);
d = [a];
}
输出结果:
参考
- ^ab#1 https://zhuanlan.zhihu.com/c_1279094787908583424
- ^for each...in - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for_each...in
- ^for await...of - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for-await...of
- ^while - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/while
- ^do...while - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/do...while
- ^for - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for
- ^for...of https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of
- ^迭代协议 - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols
- ^for...in - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/statements/for...in
- ^break - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/statements/break
- ^continue - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/statements/continue
- ^label - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/statements/label