学习 lodash 源码整体架构,打造属于自己的函数式编程类库

这是 学习源码整体架构系列 第三篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。

上上篇文章写了 jQuery源码整体架构 ,学习 jQuery 源码整体架构,打造属于自己的 js 类库

上一篇文章写了 underscore源码整体架构 ,学习 underscore 源码整体架构,打造属于自己的函数式编程类库

感兴趣的读者可以点击阅读。

underscore 源码分析的文章比较多,而  lodash 源码分析的文章比较少。原因之一可能是由于  lodash 源码行数太多。注释加起来一万多行。

分析 lodash 整体代码结构的文章比较少,笔者利用谷歌、必应、  github 等搜索都没有找到,可能是找的方式不对。于是打算自己写一篇。平常开发大多数人都会使用  lodash ,而且都或多或少知道,  lodash 比  underscore 性能好,性能好的主要原因是使用了惰性求值这一特性。

本文章学习的 lodash 的版本是:  v4.17.15 。  unpkg.com 地址 https://unpkg.com/lodash@4.17.15/lodash.js

文章篇幅可能比较长,可以先收藏再看。

导读:

文章主要学习了 runInContext() 导出  _ lodash 函数使用  baseCreate 方法原型继承  LodashWrapper 和  LazyWrapper ,  mixin 挂载方法到  lodash.prototype 、后文用结合例子解释  lodash.prototype.value(wrapperValue) 和  Lazy.prototype.value(lazyValue) 惰性求值的源码具体实现。

匿名函数执行

暴露 lodash

runInContext 函数

这里的简版源码,只关注函数入口和返回值。

可以看到申明了一个 runInContext 函数。里面有一个  lodash 函数,最后处理返回这个  lodash 函数。

再看 lodash 函数中的返回值  newLodashWrapper(value)

LodashWrapper 函数

设置了这些属性:

__wrapped__ :存放参数  value

__actions__ :存放待执行的函数体  func , 函数参数  args ,函数执行的  this 指向  thisArg

__chain__ 、  undefined 两次取反转成布尔值  false ,不支持链式调用。和  underscore 一样,默认是不支持链式调用的。

__index__ :索引值 默认 0。

__values__ :主要  clone 时使用。

接着往下搜索源码, LodashWrapper , 会发现这两行代码。

接着往上找 baseCreatebaseLodash 这两个函数。

baseCreate 原型继承

笔者画了一张图,表示这个关系。

衍生的 isObject 函数

判断 typeofvalue 不等于  null ,并且是  object 或者  function

Object.create() 用法举例

面试官问:能否模拟实现JS的new操作符 之前这篇文章写过的一段。

笔者之前整理的一篇文章中也有讲过,可以翻看JavaScript 对象所有API解析

MDN Object.create()

Object.create(proto,[propertiesObject]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的 proto 。它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是  undefined )。

对于不支持 ES5 的浏览器,  MDN 上提供了  ployfill 方案。

lodash 上有很多方法和属性,但在  lodash.prototype 也有很多与  lodash 上相同的方法。肯定不是在  lodash.prototype 上重新写一遍。而是通过  mixin 挂载的。

mixin

mixin 具体用法

添加来源对象自身的所有可枚举函数属性到目标对象。如果 object 是个函数,那么函数方法将被添加到原型链上。

注意: 使用 _.runInContext 来创建原始的 lodash 函数来避免修改造成的冲突。

添加版本

0.1.0

参数

[object=lodash] (Function|Object): 目标对象。
source (Object): 来源对象。
[options={}] (Object): 选项对象。
[options.chain=true] (boolean): 是否开启链式操作。

返回

(*): 返回 object.

mixin 源码

mixin源码,后文注释解析

接下来先看衍生的函数。

其实看到具体定义的函数代码就大概知道这个函数的功能。为了不影响主线,导致文章篇幅过长。具体源码在这里就不展开。

感兴趣的读者可以自行看这些函数衍生的其他函数的源码。

mixin 衍生的函数 keys

mixin 函数中 其实最终调用的就是  Object.keys

mixin 衍生的函数 baseFunctions

返回函数数组集合

mixin 衍生的函数 isFunction

判断参数是否是函数

mixin 衍生的函数 arrayEach

类似 [].forEarch

mixin 衍生的函数 arrayPush

类似 [].push

mixin 衍生的函数 copyArray

拷贝数组

mixin 源码解析

lodash 源码中两次调用  mixin

结合两次调用 mixin 代入到源码解析如下 mixin源码及注释

小结:简单说就是把 lodash 上的静态方法赋值到  lodash.prototype 上。分两次第一次是支持链式调用(  lodash.after 等  153 个支持链式调用的方法),第二次是不支持链式调用的方法(  lodash.add 等  152 个不支持链式调用的方法)。

lodash 究竟在 .prototype挂载了多少方法和属性

再来看下 lodash 究竟挂载在  _ 函数对象上有多少静态方法和属性,和挂载  _.prototype 上有多少方法和属性。

使用 forin 循环一试便知。看如下代码:

其实就是上文提及的 lodash.after 等  153 个支持链式调用的函数 、  lodash.add 等  152 不支持链式调用的函数赋值而来。

相比 lodash 上的静态方法多了  12 个,说明除了  mixin 外,还有  12 个其他形式赋值而来。

支持链式调用的方法最后返回是实例对象,获取最后的处理的结果值,最后需要调用 value 方法。

笔者画了一张表示 lodash 的方法和属性挂载关系图。

请出贯穿下文的简单的例子

也就是说这里  lodash 聪明的知道了最后需要几个值,就执行几次  map 循环,对于很大的数组,提升性能很有帮助。

而  underscore 执行这段代码其中  map 执行了5次。如果是平常实现该功能也简单。

而相比 lodash 这里的  map 执行了  5 次。

简单说这里的 map 方法,添加  LazyWrapper 的方法到  lodash.prototype 存储下来,最后调用  value 时再调用。具体看下文源码实现。

添加 LazyWrapper 的方法到  lodash.prototype

主要是如下方法添加到到 lodash.prototype 原型上。

具体源码及注释

小结一下,写了这么多注释,简单说:其实就是用 LazyWrapper.prototype 改写原先在  lodash.prototype 的函数,判断函数是否需要使用惰性求值,需要时再调用。

读者可以断点调试一下,善用断点进入函数功能,对着注释看,可能会更加清晰。

断点调试的部分截图

链式调用最后都是返回实例对象,实际的处理数据的函数都没有调用,而是被存储存储下来了,最后调用 value 方法,才执行这些函数。

lodash.prototype.value 即 wrapperValue

如果是惰性求值,则调用的是 LazyWrapper.prototype.value 即  lazyValue

LazyWrapper.prototype.value 即 lazyValue 惰性求值

lazyValue源码及注释

笔者画了一张 lodash 和  LazyWrapper 的关系图来表示。 

小结: lazyValue 简单说实现的功能就是把之前记录的需要执行几次,把记录存储的函数执行几次,不会有多少项数据就执行多少次,而是根据需要几项,执行几项。也就是说以下这个例子中,  map 函数只会执行  3 次。如果没有用惰性求值,那么  map 函数会执行  5 次。

总结

行文至此,基本接近尾声,最后总结一下。

文章主要学习了 runInContext() 导出  _ lodash 函数使用  baseCreate 方法原型继承  LodashWrapper 和  LazyWrapper ,  mixin 挂载方法到  lodash.prototype 、后文用结合例子解释  lodash.prototype.value(wrapperValue) 和  Lazy.prototype.value(lazyValue) 惰性求值的源码具体实现。

分享一个只知道函数名找源码定位函数申明位置的  VSCode  技巧 :  Ctrl+p 。输入  @functionName 定位函数  functionName 在源码文件中的具体位置。如果知道调用位置,那直接按  alt+鼠标左键 即可跳转到函数申明的位置。

如果读者发现有不妥或可改善之处,再或者哪里没写明白的地方,欢迎评论指出。另外觉得写得不错,对您有些许帮助,可以点赞、评论、转发分享,也是对笔者的一种支持。万分感谢。

推荐阅读

lodash github仓库

lodash 官方文档

lodash 中文文档

打造一个类似于lodash的前端工具库

惰性求值——lodash源码解读

luobo tang:lazy.js 惰性求值实现分析

lazy.js github 仓库

本文章学习的  lodash 的版本  v4.17.15 unpkg.com 链接

关于

作者:常以 若川 为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。

个人博客 http://lxchuan12.github.io  使用  vuepress 重构了,阅读体验可能更好些

https://github.com/lxchuan12/blog ,相关源码和资源都放在这里,求个 star ^_^~

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章