JavaScript 循环语句

106 阅读7分钟

JavaScript 支持不同类型的循环:

  • for - 循环代码块一定的次数
  • for/in - 循环遍历对象的属性
  • for/of - 循环遍历迭代的对象
  • while - 当指定的条件为 true 时循环指定的代码块
  • do/while - 同样当指定的条件为 true 时循环指定的代码块

for 循环

for 循环是您在希望创建循环时常会用到的工具。 下面是 for 循环的语法:

for (语句 1; 语句 2; 语句 3){
    被执行的代码块
}

语句 1 在执行循环体(代码块)前开始先执行一次 语句 2 定义运行循环体(代码块)的条件 语句 3 在循环体(代码块)已被执行之后执行

for (var i=0; i<5; i++){
      x=x + "该数字为 " + i + "<br>";
}

从上面的例子中,您可以看到: Statement 1 在循环开始之前设置变量 (var i=0) Statement 2 定义循环运行的条件(i 必须小于 5) Statement 3 在每次代码块已被执行后增加一个值 (i++) 尝试一下 »

语句1 通常我们会使用语句 1 初始化循环中所用的变量 (var i=0) 语句1 是可选的,也可以在语句1中初始化任意值(或者多个):

for (var i = 0, len = cars.length; i < len; i++) {
  document.write(cars[i] + "<br>")
}


var j = 2, len = cars.length
for (; j < len; j++) {
  document.write(cars[j] + "<br>")
}

语句2 通常语句 2 用于评估初始变量的条件 语句2 同样是可选的 如果语句 2 返回 true,则循环再次开始,如果返回 false,则循环将结束 ⚠️**注意: **如果您省略了语句 2,那么必须在循环内提供 break。否则循环就无法停下来。这样有可能令浏览器崩溃

语句3 通常语句 3 会增加初始变量的值 语句3 也是可选的 语句3 有多种用法。增量可以是负数 (i--),或者更大 (i=i+15) 语句3 也可以省略(比如当循环内部有相应的代码时

var i=0,len=cars.length;
for (; i<len; ){ 
    document.write(cars[i] + "<br>");
    i++;
}

无语句的无限循环

var i = 0
for (;;) {
  console.log(i++)
}

打印九九乘法表

<script>
  //九九乘法表
    document.write("<table border='1' cellpadding='1' cellspacing='1'>")
    for (var i = 1; i <= 9; i++) {
      document.write("<tr>")
      for (var j = 1; j <= i; j++) {
        document.write("<td>")
        document.write(i + "*" + j + "=" + i * j);
        document.write("</td>")
      }
    }
    document.write("</tr>")
    document.write("</table>")
</script>

斐波那契数列12个月的总和

function getFibonacciNumber1(x) {
  var sum = 0,num1 = 1,num2 = 1
  for (i = 3; i <= 12; i++) {
    sum = num1 + num2
    num2 = num1
    num1 = sum
  }
  return sum
}
console.log(getFibonacciNumber1(12))



function getFibonacciNumber2(x) {
  if (x < 3) return 1;
  return getFibonacciNumber2(x - 1) + getFibonacciNumber2(x - 2)
}
console.log(getFibonacciNumber2(12)); //144




function getFibonacciNumber3(num) {
  var sum =0 ,i = 0
  function* fibonacci() {
    let [prev, cur] = [0, 1]
    while (true) {
      [prev, cur] = [cur, prev + cur]
      yield prev
    }
  }

  for (let item of fibonacci()) {
    if (i >= num) break
    sum = item
    i++
  }
  return sum
}
console.log(getFibonacciNumber3(12));

统计字符出现的次数

function getStrFrequency(str) {
  var str = str,
    json = {};
  for (var i = 0; i < str.length; i++) {
    json[str[i]] = json[str[i]] + 1 || 1;
  }
  return json;
}
console.log(getStrFrequency("e247y83jndnjgkdu8ue4r78y7583hujrfu8eht89r3"));
function getStrFrequency2(str) {
var obj = {};
if (str.length<1) return obj;
  for (var i = 0; i <= str.length -1 ; i++) {
    var strItem = str.charAt(i);
    if (obj[strItem] && obj[strItem].value == strItem) {
      obj[strItem].count = ++obj[strItem].count;
    } else {
      obj[strItem] = {};
      obj[strItem].count = 1;
      obj[strItem].value = strItem;
      obj[strItem].name = "哈哈";
    }
  }
  return obj;
}

var obj = getStrFrequency2("e247y83jndnjgkdu8ue4r78y7583hujrfu8eht89r3")
console.log(obj);
for (key in obj) {
  console.log(obj[key].value + "=" + obj[key].count + " "); // a=4  b=3  c=4  d=2  f=1  g=1  h=1
}

for/in 循环

for/in 语句循环可以遍历对象的所有可枚举属性(包括对象自有属性和继承的属性) 不过需要注意的是,使用for...in循环遍历对象属性时返回的属性会因为各个浏览器不同导致对象属性遍历的顺序有可能不是当初构建时的顺序

var person = {
  fname: "John",
  lname: "Doe",
  age: 25,
  __proto__: {
    lastName: 'xie'
  }
}
Object.prototype.methods = function () { };
for (var x in person) {
  console.log(`属性=${x} 值=${person[x]}`);
}


var name = ["张一山", "二腿子", "张三", "李四"];
Array.prototype.newMethods = function () { }
for (y in name) { //var可以省略,y为属性名
  console.log(`属性=${y} 值=${name[y]}`);
}

/*
属性=fname 值=John
属性=lname 值=Doe
属性=age 值=25
属性=lastName 值=xie
属性=methods 值=function () { }


属性=0 值=张一山
属性=1 值=二腿子
属性=2 值=张三
属性=3 值=李四
属性=newMethods 值=function () { }
属性=methods 值=function () { }
*/
Array.prototype.say = () => { };
Array.prototype.eat = () => { };
const array = ["foo", "bar", "baz"];

for (const value in array) {
  console.log(value, array[value]);
  /*
  0 foo
  1 bar
  2 baz
  say [Function]
  eat [Function]
  */
}

for (const iterator of array) {
  console.log(iterator);
  /*   
  foo
  bar
  baz 
  */
}

array.forEach(element => {
  console.log(element);
  /*
  foo
  bar
  baz
  */
});

⚠️**注意: **for...in 不仅查找原本自身的举属性,它还从构造函数的原型中查找继承的枚举属性

for/of 循环

JavaScript for/of 语句循环遍历迭代器的属性:

//———————————————————— 遍历对象 ————————————————————
// for (var variable of iterable) { statement }
// var 可以省略
// variable:每个迭代的属性值被分配给该变量。
// iterable:一个具有可枚举属性并且可以迭代的对象

function Teacher() { this.name = "John"; }
Teacher.prototype = { constructor: Teacher, name: "John", age: 36 };
var teacher = new Teacher();
Object.defineProperty(teacher, "name", {
  value: "John",
  enumerable: true
});
Object.defineProperty(teacher, "age", {
  value: 36,
  enumerable: true
});
for (var [k, v] of Object.entries(teacher)) {
  console.log(`teacher: 项属性=${k} 项值=${v}`);
}

//———————————————————— 遍历迭代器 ————————————————————
var student = { name: "John", age: 25 }
Object.defineProperty(student, "sex", {
  value: "female",
  enumerable: true
});
Object.defineProperty(student, "age", { //设置年龄不可以枚举
  value: "female",
  enumerable: false
});
// for (var variable of Object.entries(student)) { //var可以省略
//   console.log(`student: 项属性=${variable[0]} 项值=${variable[1]}`);
// }
for (var [k, v] of Object.entries(student)) {
  console.log(`student: 项属性=${k} 项值=${v}`);
}

//———————————————————— 遍历数组 ————————————————————
var name = ["张一山", "二腿子", "张三", "李四"];
for (var v of name) {
  console.log(`name:项值 = ${v}`);
}

/*
teacher: 项属性=name 项值=John
teacher: 项属性=age 项值=36

student: 项属性=name 项值=John
student: 项属性=sex 项值=female

name:项值 = 张一山
name:项值 = 二腿子
name:项值 = 张三
name:项值 = 李四
*/

⚠️注意: for...of 不考虑构造函数原型的不可枚举属性。它只需要查找可枚举属性并将其打印出来 ⚠️注意: for...of 更多用于特定于集合(如数组和对象),但不包括所有对象 ⚠️注意: 任何具有Symbol.iterator属性的元素都是可迭代的 ⚠️注意: ES6的for...of与JavaScript原有的for...in的不同在于:for...of的i循环出来的是键值,而for...in的i循环出来的是键名

遍历Arrays(数组)

[Array.prototype@@iterator](developer.mozilla.org/zh-CN/docs/…) Arrays(数组) 就是类列表(list-like) 对象 数组原型上有各种方法,允许对其进行操作,比如修改和遍历等操作。下面手在一个数组上进行的 for...of 操作:

const iterable = ["一缕清风", "Ken", "Kenguba"];
for (const value of iterable) {
  console.log(value);
}
/* 
一缕清风
Ken
Kenguba 
*/


var arr = ["a", "b", "c", "d", "e"];
var aArr = arr[Symbol.iterator]();
// 浏览器必须支持 for...of 循环
for (var letter of aArr) {
  console.log(letter);
}
/* 
a
b
c
d
e 
*/


// arr.next().value //报错 arr.next is not a function
var bArr = arr[Symbol.iterator]();
console.log(bArr.next().value); 

遍历Maps(映射)

[Map.prototype@@iterator](developer.mozilla.org/zh-CN/docs/…) Map对象就是保存key-value(键值对)。对象和原始值可以用作 key(键)或 value(值) Map对象根据其插入方式迭代元素。换句话说, for...of 循环将为每次迭代返回一个 key-value(键值) 数组

const iterable = new Map([
  ["one", 1],
  ["two", 2],
])
iterable.set("third",3);
for (const [key, value] of iterable) {
  console.log(`key=${key} value=${value}`);
}
/* 
key=one value=1
key=two value=2
key=third value=3
*/


const map = new Map();
map.set("0", "foo");
map.set(1, "bar");
const iterator1 = map[Symbol.iterator]();
for (const item of iterator1) {
  console.log(item);
}
/* 
[ '0', 'foo' ]
[ 1, 'bar' ] 
*/

遍历Set(集合)

[Set.prototype@@iterator](developer.mozilla.org/zh-CN/docs/…) Set(集合) 对象允许你存储任何类型的唯一值,这些值可以是原始值或对象。 Set(集合) 对象只是值的集合 Set(集合) 元素的迭代基于其插入顺序 Set(集合) 中的值只能发生一次。如果您创建一个具有多个相同元素的 Set(集合) ,那么它仍然被认为是单个元素

const iterable = new Set([2, 1, 2, 2, 1, 3]);

for (const value of iterable) {
  console.log(value);
}
/* 
2
1
3
*/



const set1 = new Set();
set1.add(1);
set1.add("Kenguba");
const iterator1 = set1[Symbol.iterator]();
console.log(iterator1.next().value);
console.log(iterator1.next().value);
/* 
1
Kenguba
*/

**⚠️注意: **尽管我们的 Set(集合) 有多个1和2 ,但输出的只有唯一的1和2

String(字符串)

[String.prototype@@iterator](developer.mozilla.org/zh-CN/docs/…) 字符串用于以文本形式存储数据

const iterable = "javascript";
for (const value of iterable) {
  console.log(value);
}
/* 
j
a
v
a
s
c
r
i
p
t 
*/


const str = "The quick red fox jumped over the lazy dog's back.";
const iterator = str[Symbol.iterator]();
let theChar = iterator.next();
while (!theChar.done && theChar.value !== " ") {
  console.log(theChar.value);
  theChar = iterator.next();
}
/* 
T
h
e 
*/

遍历Arguments Object(参数对象)

把一个参数对象看作是一个类数组(array-like)对象,并且对应于传递给函数的参数。这是一个用例:

function args() {
  for (const arg of arguments) {
    console.log(arg);
  }
}
args("a", "b", "c")
/* 
a
b
c 
*/

遍历Generators(生成器)

生成器是一个函数,它可以退出函数,稍后重新进入函数

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

for (const g of generator()) {
  console.log(g);
}

function*定义了一个生成器函数,该函数返回生成器对象(Generator object)。更多生成器相关的信息点击这里

退出迭代

JavaScript 提供了四种已知的终止循环执行的方法: break continue return throw。让我们来看一个例子:

const iterable = ['mini', 'mani', 'mo'];
 
for (const value of iterable) {
  console.log(value);
  break;
}

/*
mini
*/

普通对象不可迭代

for...of 循环仅适用于迭代。 而普通对象不可迭代。 我们来看一下:

const obj = { fname: "foo", lname: "bar" };

for (const value of obj) {
  // TypeError: obj[Symbol.iterator] is not a function
  console.log(value);
}

普通对象怎么转成可迭代?

var obj = { fname: "foo", lname: "bar" }
console.log(Object.entries(obj))

Javascript的枚举属性

JavaScript中对象的属性分为两种:数据属性访问器属性。然后根据具体的上下文环境的不同,又可以将属性分为:原型属性实例属性。原型属性是定义在对象的原型(prototype)中的属性,而实例属性一方面来自构造的函数中,然后就是构造函数实例化后添加的新属性。 在JavaScript中除了检测对象的属性是否存在,还会经常对对象的属性进行遍历(枚举)。而在JavaScript中遍历一个对象的属性并不太简单,主要有两个原因:

  • JavaScript中的对象通常都处在某个原型链中,它会从一个或多个的上层原型上继承一些属性
  • JavaScript中的属性不光有值,它还有一些除了值以外的其他特性,其中一个影响属性遍历的特性就是[[Enumerable]],如果该值为true,则这个属性是可枚举的,否则反之

其实这几个方法之间的差异主要在属性是否可可枚举,是来自己原型,还是实例

方法适用范围描述
for...in数组、对象获取可枚举的实例和原型属性名
for...of可迭代对象
Array, Map, Set, arguments等返回属性值
Object.keys()数组,对象返回可枚举的实例属性名组成的数组
Object.getOwnPropertyNames() 数组、对象返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组,但不会获取原型链上的属性
Array.prototype.say = () => { };
Array.prototype.eat = () => { };
const array = ["foo", "bar", "baz"];
array.play = function (params) { }

console.log(Object.getOwnPropertyNames(array));  //[ '0', '1', '2', 'length', 'play' ]
console.log(Object.keys(array));                 //[ '0', '1', '2', 'play' ]
console.log(Object.getOwnPropertyNames(array));  //[ '0', '1', '2', 'length', 'play' ]

console.log(array.propertyIsEnumerable("play")); //true
console.log(array.propertyIsEnumerable("say"));  //false
console.log(array.propertyIsEnumerable("foo"));  //false

while 循环

while 循环会在指定条件为真时循环执行代码块

while (条件){
    需要执行的代码
}

本例中的循环将继续运行,只要变量 i 小于 5:

while (i<5){
    x=x + "The number is " + i + "<br>";
    i++;
}
while (true) { } // 相当于 for(;;){ }

**⚠️注意: **如果您忘记增加条件中所用变量的值,该循环永远不会结束。这可能导致浏览器崩溃

do/while 循环

do/while 循环是 while 循环的变体。该循环会在检查条件是否为真之前执行一次代码块,然后如果条件为真的话,就会重复这个循环

do{
    需要执行的代码
}while (条件)

下面的例子使用 do/while 循环。该循环至少会执行一次(即使条件为 false),因为代码块会在条件被测试前执行:

do{
    x=x + "The number is " + i + "<br>"
    i++
}while (i<5);

break语句

break语句可用于跳出循环

for (i = 0; i < 10; i++) {
  if (i == 3) { break}
  x = "The number is " + i
  console.log(x)
}

continue 语句

continue语句中断循环中的迭代,如果出现了指定的条件,然后继续循环中的下一个迭代

for (i = 0; i <= 10; i++) {
  if (i == 3) continue;
  console.log("The number is " + i )
}

JavaScript 标签

JavaScript 标签可以对 JavaScript 语句进行标记 如需标记 JavaScript 语句,请在语句之前加上冒号:

label:
statements

break 和 continue 语句仅仅是能够跳出代码块的语句

break labelname; 
continue labelname;

⚠️注意: 代码块指的基本上是{}大括号之间 break 作用是跳出代码块, 可以用于循环和 switch 等代码块 continue 作用是进入下一个迭代, 只能用于循环的代码块

默认标签的情况

除了默认标签情况,其他时候必须要有名标签,否则会有惊喜

  • 当 break 和 continue 同时用于循环时,没有加标签,此时默认标签为当前"循环"的代码块
  • 当 break 用于 switch 时,默认标签为当前的 switch 代码块

有名标签的情况

用 break labelname 会跳出labelname的代码块

cars = ["BMW", "Volvo", "Saab", "Ford"];
list: {
  console.log(cars[0]);
  console.log(cars[1]);
  console.log(cars[2]);
  break list;
  console.log(cars[3]);
  console.log(cars[4]);
  console.log(cars[5]);
}
/* 
BMW;
Volvo;
Saab;
*/

使用break和continue在多层循环的时候控制外层循环

outerloop: for (var i = 0; i < 10; i++) {
  innerloop: for (var j = 0; j < 10; j++) {
    if (j > 3) {
      break;
    }
    if (i == 2) {
      break innerloop;
    }
    if (i == 4) {
      break outerloop;
    }
    console.log("i=" + i + " j=" + j + "");
  }
}