lodash是否是数组_lodash源码解析:for家族

本文深入分析lodash中for家族方法,包括forOwn、forOwnRight、forEach及其依赖的内部方法。探讨它们如何处理数组、类数组对象和普通对象的迭代,并指出forEach对数组和对象的兼容性优化。同时提供了相关源码仓库链接。
摘要由CSDN通过智能技术生成

b5d164865dabf2b0e2543273eea300c8.png

本篇继续分析下 for 家族的方法,for 方法的主要目的是实现对数组、类数组对象和普通对象的迭代。包括forEachforEachRightforOwnforOwnRight及依赖的基础方法。

对应源码分析已推到 github 仓库: https://github.com/MageeLin/lodash-source-code-analysis

依赖路径图

for 家族方法的依赖路径图如下所示:

从路径图可以发现,forOwnforOwnRight 方法没有依赖其他方法。而 forEach 却依赖了几个内部方法 arrayEachbaseEachbaseForOwnbaseFor。同理 forEachRight 也是相似的方式。

forOwn

按照命名来看,其实是有 baseForOwn 这个基础方法的,但是 forOwn 并没有引用它,而是只用一个文件来实现。怀疑是之前用过但后来优化成了 Object.keys

forOwnforOwnRight 都是分成 2 步实现:

  1. Object.keys(object) 取到所有的对象自有可迭代属性键。
  2. 按照正正序或者反序迭代,同时调用 iteratee
注意: iteratee 中返回 false 并不能打断迭代。

forOwn

/**
 * 使用 iteratee 遍历一个 object 自身的可枚举属性键。
 * `iteratee` 会传入3个参数:(value, key, object)。
 * 如果返回 `false`,iteratee 会提前退出遍历。
 *
 * @since 0.3.0
 * @category Object
 * @param {Object} object 要迭代遍历的object
 * @param {Function} iteratee 每次迭代调用 iteratee 函数
 * @see forEach, forEachRight, forIn, forInRight, forOwnRight
 * @example
 *
 * function Foo() {
 *   this.a = 1
 *   this.b = 2
 * }
 *
 * Foo.prototype.c = 3
 *
 * forOwn(new Foo, function(value, key) {
 *   console.log(key)
 * })
 * // => Logs 'a' then 'b' (不能保证迭代顺序).
 */
function forOwn(object, iteratee) {
  object = Object(object);
  // 直接用 Object.keys 来获取键,然后来迭代
  // 这里的 iteratee 不需要返回 false 打断迭代
  Object.keys(object).forEach((key) => iteratee(object[key], key, object));
}

export default forOwn;

forOwnRight

/**
 * 此方法类似 `forOwn`,但是它是反方向开始遍历 `object` 的。
 *
 * @since 2.0.0
 * @category Object
 * @param {Object} object 要迭代遍历的object
 * @param {Function} iteratee 每次迭代调用 iteratee 函数
 * @returns {Object} 返回 `object`.
 * @see forEach, forEachRight, forIn, forInRight, forOwn
 * @example
 *
 * function Foo() {
 *   this.a = 1
 *   this.b = 2
 * }
 *
 * Foo.prototype.c = 3
 *
 * forOwnRight(new Foo, function(value, key) {
 *   console.log(key)
 * })
 * // => 如果 `forOwn` 先打印 'a' 后打印 'b',则此方法先打印 'b' 后打印 'a'
 */
function forOwnRight(object, iteratee) {
  if (object == null) {
    return;
  }
  // 同样是用 Object.keys
  const props = Object.keys(object);
  let length = props.length;
  while (length--) {
    // 同样不可以用 iteratee 返回 false 来打断迭代
    iteratee(object[props[length]], iteratee, object);
  }
}

export default forOwnRight;

forEach

forEach 实现时用了不同的迭代方法进行优化,分为下面三种情况:

  1. Array.isArray(collection) 来判断是否是数组类型,是的话就执行 arrayEach
  2. isArrayLike(collection) 来判断是不是类数组对象,是的话就 baseEach 直接迭代;
  3. 以上两种情况都不是,就当成普通对象,用 baseFor 来迭代;
forEachRight 的实现与 forEach 基本相同,只不过从后往前迭代而已。

forEach

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

/**
 * 调用 `iteratee` 遍历 `collection` 中的每个元素,
 *  iteratee 调用3个参数: (value, index|key, collection)。
 * 如果迭代函数(iteratee)显式的返回`false`,迭代会提前退出。
 *
 * **注意:** 与其他"Collections"方法一样,类似于数组的表现,对象的 "length" 属性也会被遍历。
 * 如果想避免这种情况,可以用 `forIn` 或者 `forOwn` 代替。
 *
 * @since 0.1.0
 * @alias each
 * @category Collection
 * @param {Array|Object} collection 要迭代的collection
 * @param {Function} iteratee 每一次迭代时调用
 * @returns {Array|Object} 返回`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' (不能保证迭代顺序).
 */
function forEach(collection, iteratee) {
  // 如果是数组就用简单的数组迭代方式arrayEach,不是数组就用baseEach
  const func = Array.isArray(collection) ? arrayEach : baseEach;
  return func(collection, iteratee);
}

export default forEach;

arrayEach

/**
 * `forEach` 对于array格式实现的特殊版本
 *
 * @private
 * @param {Array|Object} collection 要迭代的array
 * @param {Function} iteratee 每一次迭代时调用
 * @returns {Array|Object} 返回 `array`
 */
function arrayEach(array, iteratee) {
  let index = -1;
  const length = array.length;

  // 迭代
  while (++index < length) {
    // 使用iteratee来调用每个元素,同时如果显式返回false,终止迭代
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  // 跟js的forEach不同,最后会把array返回
  return array;
}

export default arrayEach;

baseEach

import baseForOwn from './baseForOwn.js';
import isArrayLike from '../isArrayLike.js';

/**
 * `forEach` 的基础实现。
 *
 * @private
 * @param {Array|Object} collection 要迭代的collection
 * @param {Function} iteratee 每一次迭代时调用
 * @returns {Array|Object} 返回 `collection`
 */
function baseEach(collection, iteratee) {
  if (collection == null) {
    return collection;
  }
  // 如果是类数组对象,就换baseForOwn来迭代
  if (!isArrayLike(collection)) {
    return baseForOwn(collection, iteratee);
  }
  const length = collection.length;
  const iterable = Object(collection);
  let index = -1;

  // 迭代
  while (++index < length) {
    // 使用iteratee来调用每个元素,同时如果显式返回false,终止迭代
    if (iteratee(iterable[index], index, iterable) === false) {
      break;
    }
  }
  // 同样最后把collection返回
  return collection;
}

export default baseEach;

baseForOwn

import baseFor from './baseFor.js';
import keys from '../keys.js';

/**
 * `forOwn` 的基础实现.
 *
 * @private
 * @param {Object} object 要迭代遍历的object
 * @param {Function} iteratee 每次迭代调用 iteratee 函数
 * @returns {Object} 返回 `object`.
 */
function baseForOwn(object, iteratee) {
  // 调用baseFor,keys参数说明是获取自身可枚举属性
  return object && baseFor(object, iteratee, keys);
}

export default baseForOwn;

baseFor

/**
 * `baseForOwn` 的基础实现,它迭代由 `keysFunc` 返回的 `object`的属性,
 * 并为每个属性调用 `iteratee` 。
 * `iteratee` 可以通过显式返回 `false` 来提前退出迭代。
 * @private
 * @param {Object} object 要迭代遍历的object
 * @param {Function} iteratee 每次迭代调用 iteratee 函数
 * @param {Function} keysFunc 获取 `object` 的键的函数.
 * @returns {Object} 返回 `object`.
 */
function baseFor(object, iteratee, keysFunc) {
  const iterable = Object(object);
  // 与其他for和each方法的区别在这,有一个专门的获取键的方法
  // 用于区别获取什么类型的键
  const props = keysFunc(object);
  let { length } = props;
  let index = -1;

  // 迭代
  while (length--) {
    const key = props[++index];
    // 使用iteratee来调用每个元素,同时如果显式返回false,终止迭代
    if (iteratee(iterable[key], key, iterable) === false) {
      break;
    }
  }
  return object;
}

export default baseFor;

思考

ECMA262 明确规定了,ArraySetMap 的实例是可迭代对象,但是 Object 的实例不是,所以原生的 forEach 是无法应用在普通的对象上的,lodash 在这里把 Object 做了兼容。

原生想迭代一个对象的所有属性可以如下几种:

  • for...in
  • Object.keys(o)
  • Object.values(o)
  • Object.entries(o)
  • Object.getOwnPropertyNames(o)

前端记事本,不定期更新,欢迎关注!

  • 微信公众号: 林景宜的记事本
  • 博客:林景宜的记事本
  • 掘金专栏:林景宜的记事本
  • 知乎专栏: 林景宜的记事本
  • Github: MageeLin

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值