JS中循环、递归、迭代、遍历、枚举的概念辨析

359 阅读7分钟

一、循环

1.循环的概念

循环是程序设计语言中反复执行某些代码的一种计算机处理过程

以上是百科中对循环的解释,其含义也很简单。重复执行一段代码就是循环。

其实这就是第一种理解,即循环是一种抽象的概念,或者说是代码的一种执行方式。

2.循环语句

在编程当中提到循环就自然联想的循环语句,循环语句由循环体循环终止条件两部分组成。重复执行的语句就是循环体,而决定循环是否继续下去的就是循环终止条件

在JS当中就有forwhiledo-while等循环语句

  let index = 0
  while(index < 4){
      console.log(index);
      index ++
  }

上面是JS中的一个while循环,其中index < 4就是循环终止条件,而大括号中的内容就是循环体。

3.总结

在JS当中,循环广义上理解就是一种“重复执行代码”这种行为。狭义的理解就是while等循环语句。

二、递归

1.什么是递归?

首先要区分递归和函数自调用

以下是百科中对递归的解释

递归在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。

我还看到一个很形象的说法:

你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开它。若干次之后,你打开面前的门后,发现只有一间屋子,没有门了。然后,你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这你把钥匙打开了几扇门

2.递归思想

递归的基本思想就是把规模大的问题转化为规模小的相似的子问题来解决。递归递归,自然就分为“递”和“归”两部分,“递”是指“递去”,即将大问题不断分解为相似的小问题的过程;“归”是指“归来”,即当无法再继续分解时,回收所有小问题的结果,最终解决一开始的大问题。

递归有所谓的三大要素:

  1. 明确函数功能
  2. 寻找递归终止条件
  3. 寻找可以用函数解决的重复子问题

对应上面的例子,函数功能就是“钥匙”;终止条件就是“没有门的屋子”;重复的子问题就是“开门”。

3.递归与循环

其实对比之前的循环不难发现,两者有许多相似之处,递归似乎就是循环,递归函数是循环体;递归终止条件是循环终止条件。

因此我总结,递归属于循环,它是一种通过函数自调用实现的特殊循环

4.总结

在JS当中的递归,它既是一种思想,也是函数的自调用。因可以说递归思想是理论层面的递归,函数自调用式实践层面的递归。

三、迭代

1.什么是迭代?

迭代的概念就比较复杂了,其在不同的领域有着不同的概念。

  • 从软件工程的角度来说,迭代是一个软件生命周期模型,迭代通常指通过多次、密集的发布更新,类似小步快跑的方法,来逐渐逼近客户真正想要的完美产品的一种项目实施方式。即我们常说的“版本迭代”。
  • 从编程的角度来说,迭代就是等于循环。
  • 从算法的角度来说,迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。我感觉其概念有些类似于JS中的数组并归

所以在JS当中,可以理解为迭代就是循环。

2.JS中的迭代器

在JS当中有迭代器的概念,按照我的理解,迭代器就是实现迭代的一种工具,它是一种跨类型的迭代方法。

  const items = document.getElementsByClassName('cl-item')

  for (const iterator of items) {
      console.log(iterator);
  }

在上面这个例子中for-of正是调用了items的迭代器,实现了迭代。

四、遍历

1.遍历的含义

首先我先从汉语词语的角度看一看“遍历”,例如古文当中有这样一种用法:“乃以是履弃之于道旁,即遍历人家捕之,若有女履者,捕之以告。”这里的遍是全面、到处的意思;而历,在这里应当作逐一、逐个地的来讲。所以这里的遍历的意思是全部逐一的。

进而引申到编程当中,比如“遍历数组”,其含义就应当是:“全部逐一的访问数组中的每个元素”。

当然根据我所查到的资料,遍历这个概念主要还是应用于树/图这种数据结构,指按照某种顺序依次访问树/图中的所有节点。只不过遍历这个概念同样适用于数组等多元素集合。当然在JS当中是鲜少涉及树/图的主要还是遍历数组等集合。

2.遍历与循环

在过去我是认为JS中的遍历与循环是同一个意思的,但是常常又觉得这两个概念是有一定的区别的,只是区别是什么我说不上来。

不过现在我尝试谈一谈它们的区别。

  const arr = [1,2,3]   
  const iterator = arr[Symbol.iterator]()

上面是一个数组以及其关联的迭代器对象,那么对于它来说下面的操作是不是循环?是,这里都使用了while这个循环语句了;但是不是遍历?我认为不是,因为并没有访问数组中的每个元素。

    const arr = [1,2,3]   
    const iterator = arr[Symbol.iterator]()

    let element = 0
    while(element < 2){
        element = iterator.next().value
        console.log(element);
    }
    //1
    //2

下面这个操作,是不是循环?是;是不是遍历?也是,因为访问了数组中所有的元素

  const arr = [1, 2, 3]

  for (const i of arr) {
   console.log(i); 
  }
  //1
  //2
  //3

所以我的结论是:遍历属于循环,或者说循环是实现遍历的一种方式。而且遍历一定是要和某个数据相绑定的,如遍历树、遍历图、遍历数组。而循环则不需要。

五、枚举

在编程当中的枚举作为名词和动词是两种不同的含义。

1.作为名词的“枚举”

当枚举作为名词的时候,指的是枚举类,当然JS当中是没有枚举类的语法的,下面展示一下TS中的枚举类。

一个熟悉的屏幕空间事件类型:

enum ScreenSpaceEventType {
	LEFT_DOWN = "鼠标左键按下事件",
  LEFT_UP = "鼠标左键弹起事件",
  LEFT_CLICK = "鼠标左键点击事件",
  LEFT_DOUBLE_CLICK = "鼠标左键双击事件",
}

当然我们在JS中也可以模拟出枚举类的效果,比如写一个枚举函数:

function ScreenSpaceEventType(key) {
  switch (key) {
    case 'LEFT_DOWN':
      return '鼠标左键按下事件'
    case 'LEFT_UP':
      return '鼠标左键弹起事件'
    case 'LEFT_CLICK':
      return '鼠标左键点击事件'
    case 'EFT_DOUBLE_CLICK':
      return '鼠标左键双击事件'
  }
}

也可以模拟一个枚举类:

class ScreenSpaceEventType {
 static LEFT_DOWN = '鼠标左键按下事件'
 static LEFT_UP = '鼠标左键弹起事件'
 static LEFT_CLICK = '鼠标左键点击事件'
 static LEFT_DOUBLE_CLICK = '鼠标左键双击事件'
}

2.作为动词的“枚举”

枚举作为动词其概念其实就是“枚举法”即把所有的可能情况都列举出来。具体到JS当中枚举则是主要是指对象的一个操作,因为对象的属性有一个[[Enumerable]]特性,即属性否可枚举,并且对象有与可枚举性相关的一些方法,如keys()values()

但是当写到这里的时候,我逐渐迷茫,因为我发现JS对象属性的可枚举性决定了它能否被for-of遍历,那么这里的枚举岂非就是遍历?

或许这就是答案


参考资料

  1. 循环
  2. 循环与迭代 - JavaScript | MDN
  3. 递归算法
  4. 什么是递归和迭代
  5. 循环、递归、遍历、迭代的区别_迭代和循环的区别-CSDN博客
  6. 递归详解——让你真正明白递归的含义-CSDN博客
  7. 遍历