本篇继续分析下 for
家族的方法,for
方法的主要目的是实现对数组、类数组对象和普通对象的迭代。包括forEach
、forEachRight
、forOwn
、forOwnRight
及依赖的基础方法。
对应源码分析已推到 github
仓库: https://github.com/MageeLin/lodash-source-code-analysis
依赖路径图
for
家族方法的依赖路径图如下所示:
从路径图可以发现,forOwn
和 forOwnRight
方法没有依赖其他方法。而 forEach
却依赖了几个内部方法 arrayEach
、baseEach
、baseForOwn
和 baseFor
。同理 forEachRight
也是相似的方式。
forOwn
按照命名来看,其实是有 baseForOwn
这个基础方法的,但是 forOwn
并没有引用它,而是只用一个文件来实现。怀疑是之前用过但后来优化成了 Object.keys
。
forOwn
和 forOwnRight
都是分成 2
步实现:
Object.keys(object)
取到所有的对象自有可迭代属性键。- 按照正正序或者反序迭代,同时调用
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
实现时用了不同的迭代方法进行优化,分为下面三种情况:
Array.isArray(collection)
来判断是否是数组类型,是的话就执行arrayEach
;isArrayLike(collection)
来判断是不是类数组对象,是的话就baseEach
直接迭代;- 以上两种情况都不是,就当成普通对象,用
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 明确规定了,Array
、Set
和 Map
的实例是可迭代对象,但是 Object
的实例不是,所以原生的 forEach
是无法应用在普通的对象上的,lodash 在这里把 Object
做了兼容。
原生想迭代一个对象的所有属性可以如下几种:
for...in
Object.keys(o)
Object.values(o)
Object.entries(o)
Object.getOwnPropertyNames(o)
前端记事本,不定期更新,欢迎关注!
- 微信公众号: 林景宜的记事本
- 博客:林景宜的记事本
- 掘金专栏:林景宜的记事本
- 知乎专栏: 林景宜的记事本
- Github: MageeLin