【Lodash源码】_.map() [ map和foreach有什么区别呢?]

本文通过实例对比分析Lodash库中的_.map()和_.forEach()方法,揭示它们在遍历数组时的共同点与不同点。_.forEach()不返回新数组,直接修改原数组;而_.map()返回新数组,保持原数组不变。在源码中,当传入参数为数组时,_.forEach()会调用arrayEach函数进行处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们通过例子来分析源码和两个方法之间的区别

举例:_.forEach()

var array = [1,2,3,4,5];  
var res = _.forEach(array, function (item,index,input) {  
       input[index] = item*10;  
})  
console.log(res);//=>undefined
console.log(array);//=>[10,20,30,40,50]

举例:_.map()

var res = _.map(array, function (item,index,input) {  
       input[index] = item*10;  
})  
console.log(res);//=>[10,20,30,40,50]
console.log(array);//=>[1,2,3,4,5]

共同点:

  1. 都是循环遍历数组中的每一项
  2. 每一次执行匿名函数都支持三个参数,数组中的当前项item,当前项的索引index,原始数组input
  3. 只能遍历数组

不同点:

_.foreach()

没有返回值

对原本数组进行操作

_.map()

有返回值

克隆原本数组 ,再新数组进行操作并返回新数组

?????我在敲黑板

这个同点,请自己在下面的源码中找到原因。

有兴趣交流的朋友,在下面留言

也可加微信群,我们聊聊前端,一起进阶。也可加我微信15956574080

 

 

 

_.map()

/**
 * Creates an array of values by running each element of `array` thru `iteratee`.
 * The iteratee is invoked with three arguments: (value, index, array).
 *
 * @since 5.0.0
 * @category Array
 * @param {Array} array The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the new mapped array.
 * @example
 *
 * function square(n) {
 *   return n * n
 * }
 *
 * map([4, 8], square)
 * // => [16, 64]
 */
function map(array, iteratee) {
  let index = -1
  const length = array == null ? 0 : array.length
  const result = new Array(length)

  while (++index < length) {
    result[index] = iteratee(array[index], index, array)
  }
  return result
}

export default map

_.forEach()

注释:forEach()是判断传入的参数是否是数组,对不同类型分别进行不同操作

import arrayEach from './.internal/arrayEach.js'
import baseEach from './.internal/baseEach.js'

/**
 * Iterates over elements of `collection` and invokes `iteratee` for each element.
 * The iteratee is invoked with three arguments: (value, index|key, collection).
 * Iteratee functions may exit iteration early by explicitly returning `false`.
 *
 * **Note:** As with other "Collections" methods, objects with a "length"
 * property are iterated like arrays. To avoid this behavior use `forIn`
 * or `forOwn` for object iteration.
 *
 * @since 0.1.0
 * @alias each
 * @category Collection
 * @param {Array|Object} collection The collection to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array|Object} Returns `collection`.
 * @see forEachRight, forIn, forInRight, forOwn, forOwnRight
 * @example
 *
 * forEach([1, 2], value => console.log(value))
 * // => Logs `1` then `2`.
 *
 * forEach({ 'a': 1, 'b': 2 }, (value, key) => console.log(key))
 * // => Logs 'a' then 'b' (iteration order is not guaranteed).
 */
function forEach(collection, iteratee) {
  const func = Array.isArray(collection) ? arrayEach : baseEach
  return func(collection, iteratee)
}

export default forEach

当我们传入数组的时候则会进入arrayEach函数,处理该数组,具体如下:

/**
 * A specialized version of `forEach` for arrays.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns `array`.
 */
function arrayEach(array, iteratee) {
  let index = -1
  const length = array.length

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break
    }
  }
  return array
}

export default arrayEach

 

<think>嗯,用户问的是list、setmap区别联系。我需要先回忆一下这三者的基本概念它们在数据结构中的位置。首先,这三种都是Java集合框架中的接口,但其他语言比如Python也有类似的结构,可能需要说明通用性。不过用户可能主要关注的是Java,因为常被问到,但最好还是保持语言中立。 首先,list是有序的集合,允许重复元素,可以通过索引访问。比如ArrayListLinkedList。而set是无序的,不允许重复元素,比如HashSetTreeSet。map则是键值对的集合,每个键唯一,值可以重复,比如HashMapTreeMap。 接下来要比较它们的区别,可能需要从存储结构、元素是否重复、顺序性、查询效率等方面展开。比如list允许重复,set不允许,map的键唯一。存储结构方面,list通常是数组或链表,set可能用哈希表或树,map也是哈希表或树。 联系的话,三者都是用来存储数据的集合类,都实现了迭代的方式,可能在某些实现中共享底层结构,比如HashSet内部其实是用HashMap实现的,键存储元素,值为一个固定对象。这样可以帮助用户理解它们之间的联系。 用户可能的误区是认为setlist类似,只是去重,但实际它们的应用场景不同。比如需要快速查找是否存在某个元素,set的contains方法效率通常比list高,尤其是HashSet,时间复杂度接近O(1),而list是O(n)。 还要提到线程安全的问题,比如Java中的Vector是线程安全的list,而ArrayList不是。同样,HashSetHashMap不是线程安全的,而ConcurrentHashMap是线程安全的。不过这可能超出了基本区别的范围,但如果有余力可以简要提及。 另外,需要给出使用场景的例子。比如list适合需要保持插入顺序或需要根据索引访问的情况,set用于去重或快速查找,map用于需要通过键快速检索值的场景。 用户可能有更深层的需求,比如在面试中回答这个问题,或者在实际开发中选择合适的数据结构。因此需要强调选择正确的集合类型对性能程序正确性的重要性。 最后,总结它们的区别联系,确保回答结构清晰,对比明确,帮助用户快速理解记忆。</think>在数据结构中,`List`、`Set``Map`是三种核心容器类型,它们的区别与联系如下: --- ### **一、核心对比(以Java为例)** | **维度** | **List** | **Set** | **Map** | |----------------|-----------------------------------|-----------------------------------|-----------------------------------| | **存储结构** | 有序集合(保留插入顺序) | 无序集合(某些实现如`LinkedHashSet`保留顺序) | 键值对集合(键唯一,值可重复) | | **元素重复性** | 允许重复元素 | **禁止重复元素** | **键不可重复**,值可重复 | | **访问方式** | 通过索引(如`list.get(0)`) | 只能通过迭代器遍历 | 通过键访问(如`map.get(key)`) | | **底层实现** | 数组/链表(`ArrayList`/`LinkedList`) | 哈希表/树(`HashSet`/`TreeSet`) | 哈希表/树(`HashMap`/`TreeMap`) | | **典型用途** | 有序数据存储(如用户操作记录) | 去重(如IP黑名单) | 键值映射(如用户信息缓存) | --- ### **二、详细区别** #### **1. 元素唯一性** - **List** ```java List<Integer> list = new ArrayList<>(); list.add(1); // 允许重复 list.add(1); // 列表最终包含两个1 ``` - **Set** ```python s = set() s.add(1) # 添加成功 s.add(1) # 重复元素被忽略 ``` - **Map** ```javascript const map = new Map(); map.set("key1", 100); // 键唯一 map.set("key1", 200); // 覆盖原有值 ``` #### **2. 性能对比** | **操作** | **ArrayList** | **HashSet** | **HashMap** | |----------------|---------------|-------------|-------------| | 插入 | O(1)(尾部) | O(1) | O(1) | | 按索引/键查找 | O(1) | 不支持 | O(1) | | 按值查找 | O(n) | O(1) | 不支持 | | 删除 | O(n) | O(1) | O(1) | > 注:`LinkedList`插入/删除头部元素为O(1),`TreeSet`/`TreeMap`的查找为O(log n) #### **3. 内存占用** - **List**:连续内存分配(数组)或指针开销(链表) - **Set**:哈希表需要额外空间处理哈希冲突 - **Map**:存储键值对的额外内存开销 --- ### **三、核心联系** #### **1. 继承关系(Java)** - `List``Set`都继承自`Collection`接口 - `Map`是独立接口,但可通过`entrySet()`转换为`Set`视图 ```java Map<String, Integer> map = new HashMap<>(); Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); ``` #### **2. 实现依赖** - `HashSet`内部实际使用`HashMap`存储(值作为键的占位符) ```java // HashSet源码片段(Java) public class HashSet<E> { private transient HashMap<E,Object> map; public boolean add(E e) { return map.put(e, PRESENT) == null; // PRESENT为固定虚拟值 } } ``` #### **3. 遍历方式** - 三者均可通过迭代器遍历: ```python # Python示例 for item in list: ... # List遍历 for elem in set: ... # Set遍历 for key, value in map.items(): ... # Map遍历 ``` --- ### **四、使用场景** #### **List 适用场景** 1. 需要保留插入顺序(如消息队列) 2. 需要频繁按索引访问(如排行榜) 3. 允许重复数据(如日志记录) #### **Set 适用场景** 1. 去重需求(如统计独立IP) 2. 快速存在性检查(如敏感词过滤) 3. 集合运算(交集、并集) #### **Map 适用场景** 1. 键值映射(如用户ID→用户信息) 2. 缓存实现(如Redis内存数据库) 3. 频率统计(如统计单词出现次数) ```java Map<String, Integer> wordCount = new HashMap<>(); text.split(" ").forEach(word -> wordCount.put(word, wordCount.getOrDefault(word, 0) + 1) ); ``` --- ### **五、常见误区** 1. **“Set只是去重的List”** → `Set`的哈希实现使其`contains()`操作时间复杂度为O(1),而`ArrayList`需要O(n) 2. **“Map不能转换为List”** → 可通过`map.keySet()`获取键列表,或`map.values()`获取值列表 3. **“所有Set都是无序的”** → `LinkedHashSet`保留插入顺序,`TreeSet`按排序顺序存储 --- ### **总结** - **选择List**:需要顺序访问、允许重复时 - **选择Set**:需要去重或快速存在性检查时 - **选择Map**:需要建立键值关联关系时 理解这些差异能显著提升数据操作效率,例如在10万条数据中查找某个元素: - `ArrayList`需要遍历:约0.5ms(平均) - `HashSet`直接定位:约0.01ms
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值