0%

lodash源码解析:for家族

本篇继续分析下 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 使用 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 此方法类似 `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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* `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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* `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)

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


👆 全文结束,棒槌时间到 👇