【lodash】head源码研读解析

132 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

A modern JavaScript utility library delivering modularity, performance & extras.

lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库

一、环境准备

  • lodash 版本 v4.0.0

  • 通过 github1s 网页可以 查看 lodash - head 源码

  • 调试测试用例可以 clone 到本地

git clone https://github.com/lodash/lodash.git

cd axios

npm install

npm run test

二、结构分析

  本篇主要讲述 head 模块,head 功能由单文件实现,因此没有模块划分。

三、函数研读

获取数组 array 的第一个元素

/**
 * @since 0.1.0
 * @alias first
 * @category Array
 * @param {Array} array 要查询的数组
 * @returns {*} 返回数组 array的第一个元素
 * @see last
 * @example
 *
 * head([1, 2, 3])
 * // => 1
 *
 * head([])
 * // => undefined
 */
function head(array) {
  return (array != null && array.length)
    ? array[0]
    : undefined
}

export default head

  • 如果参数 array 不为 null 并且有 length 属性,则认为其为 Array 类型,直接取下标0处值返回,否则直接返回 undefined

四、单元测试

head 的实现可能是 lodash 里最简单的了,但是从测试用例的反映来看,一个高质量的 head 运算并不是这么简单,测试用例代码如下:

import assert from 'assert';
import lodashStable from 'lodash';
import { arrayProto, LARGE_ARRAY_SIZE } from './utils.js';
import head from '../head.js';
import first from '../first.js';

describe('head', function() {
  var array = [1, 2, 3, 4];

  it('should return the first element', function() {
    assert.strictEqual(head(array), 1);
  });

  it('should return `undefined` when querying empty arrays', function() {
    arrayProto[0] = 1;
    assert.strictEqual(head([]), undefined);
    arrayProto.length = 0;
  });

  it('should work as an iteratee for methods like `_.map`', function() {
    var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
        actual = lodashStable.map(array, head);

    assert.deepStrictEqual(actual, [1, 4, 7]);
  });

  it('should be aliased', function() {
    assert.strictEqual(first, head);
  });

  it('should return an unwrapped value when implicitly chaining', function() {
    var wrapped = _(array);
    assert.strictEqual(wrapped.head(), 1);
    assert.strictEqual(wrapped.first(), 1);
  });

  it('should return a wrapped value when explicitly chaining', function() {
    var wrapped = _(array).chain();
    assert.ok(wrapped.head() instanceof _);
    assert.ok(wrapped.first() instanceof _);
  });

  it('should not execute immediately when explicitly chaining', function() {
    var wrapped = _(array).chain();
    assert.strictEqual(wrapped.head().__wrapped__, array);
    assert.strictEqual(wrapped.first().__wrapped__, array);
  });

  it('should work in a lazy sequence', function() {
    var largeArray = lodashStable.range(LARGE_ARRAY_SIZE),
        smallArray = array;

    lodashStable.each(['head', 'first'], function(methodName) {
      lodashStable.times(2, function(index) {
        var array = index ? largeArray : smallArray,
            actual = _(array).filter(isEven)[methodName]();

        assert.strictEqual(actual, _[methodName](_.filter(array, isEven)));
      });
    });
  });
});

共8个测试点,分别是

  • 应当返回 array 的第一个元素
  • 当查询空数组时应当返回 undefined
  • 应当像类 _.map 一样的方法作为数组迭代器入参,从而对数组元素的每一项迭代处理
  • 可以被重命名
  • 隐式链接时应返回未包装的值
  • 显式链接时应返回包装值
  • 显式链接时不应立即执行
  • 应该按惰性顺序工作

前三点都很好理解,所谓像类 _.map 一样的方法作为数组迭代器入参,是指入参数组的每一项如果也是数组则其会作为入参再次被 head 递归调用,详情可以查看关于 map 的源码解析,这里不做额外说明。 image.png

从第四点开始就不再是对工具函数的功能实现,而是库函数的规范性、函数健壮性以及复杂场景下的执行性能提了更多的要求,第四点的可被重命名是两种命名方式实现了相同的功能

第五、六、七点的所谓隐式链接和显式链接是指 JavaScript 的原型链。我们知道 JavaScript 中有隐式原型 _proto_ 与显示原型 prototype 的区别。在 JavaScript 中万物皆对象,方法 Function 就是一个特殊的对象。一般对象具有属性 __proto__ 称为隐式原型,而 Function 作为一个特殊的对象,除了和其他对象一样具有 __proto__ 属性以外,它还有一个自己特有的原型属性 prototype,两者都是为了实现对象之间的继承与属性的共享而产生。所以对于 Function 来说,就产生了基于隐式原型 __proto__ 的向上或向下继承、基于显示原型 constructor’s prototype 的构造继承,因此产生了显式调用、隐式调用的区别。

第八点的惰性运算可以参考lodash - first源码研读解析

image.png

修改一下 scripts 命令 "test": "mocha -r esm test/*.test.js" ==> "test": "mocha -r esm test/head.js",只运行 head 的单元测试文件,运行结果如下

head.png