学习 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
, 会发现这两行代码。
接着往上找 baseCreate、baseLodash
这两个函数。
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
^_^~