一起养成写作习惯!这是我参与「掘金日新计划 · 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 的源码解析,这里不做额外说明。
从第四点开始就不再是对工具函数的功能实现,而是库函数的规范性、函数健壮性以及复杂场景下的执行性能提了更多的要求,第四点的可被重命名是两种命名方式实现了相同的功能
第五、六、七点的所谓隐式链接和显式链接是指 JavaScript 的原型链。我们知道 JavaScript 中有隐式原型 _proto_ 与显示原型 prototype 的区别。在 JavaScript 中万物皆对象,方法 Function 就是一个特殊的对象。一般对象具有属性 __proto__ 称为隐式原型,而 Function 作为一个特殊的对象,除了和其他对象一样具有 __proto__ 属性以外,它还有一个自己特有的原型属性 prototype,两者都是为了实现对象之间的继承与属性的共享而产生。所以对于 Function 来说,就产生了基于隐式原型 __proto__ 的向上或向下继承、基于显示原型 constructor’s prototype 的构造继承,因此产生了显式调用、隐式调用的区别。
第八点的惰性运算可以参考lodash - first源码研读解析
修改一下 scripts 命令 "test": "mocha -r esm test/*.test.js" ==> "test": "mocha -r esm test/head.js",只运行 head 的单元测试文件,运行结果如下