前言
你好,我是若川。这是
学习源码整体架构
第二篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。本篇文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。
学习源码整体架构
系列文章如下:
1.学习 jQuery 源码整体架构,打造属于自己的 js 类库
2.学习 underscore 源码整体架构,打造属于自己的函数式编程类库
3.学习 lodash 源码整体架构,打造属于自己的函数式编程类库
4.学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK
感兴趣的读者可以点击阅读。
虽然看过挺多underscore.js
分析类的文章,但总感觉少点什么。这也许就是纸上得来终觉浅,绝知此事要躬行吧。于是决定自己写一篇学习underscore.js
整体架构的文章。
本文章学习的版本是v1.9.1
。unpkg.com
源码地址:https://unpkg.com/underscore@1.9.1/underscore.js
虽然很多人都没用过underscore.js
,但看下官方文档都应该知道如何使用。
从一个官方文档_.chain
简单例子看起:
1 | 复制代码_.chain([1, 2, 3]).reverse().value(); |
看例子中可以看出,这是支持链式调用。
读者也可以顺着文章思路,自行打开下载源码进行调试,这样印象更加深刻。
链式调用
_.chain
函数源码:
1 | 复制代码_.chain = function(obj) { |
这个函数比较简单,就是传递obj
调用_()
。但返回值变量竟然是instance
实例对象。添加属性_chain
赋值为true
,并返回intance
对象。但再看例子,实例对象竟然可以调用reverse
方法,再调用value
方法。猜测支持OOP
(面向对象)调用。
带着问题,笔者看了下定义 _
函数对象的代码。
_
函数对象 支持OOP
1 | 复制代码var _ = function(obj) { |
如果参数obj
已经是_
的实例了,则返回obj
。
如果this
不是_
的实例,则手动 new _(obj)
;
再次new
调用时,把obj
对象赋值给_wrapped
这个属性。
也就是说最后得到的实例对象是这样的结构{ _wrapped: '参数obj', }
它的原型_(obj).__proto__
是 _.prototype
;
如果对这块不熟悉的读者,可以看下以下这张图(之前写面试官问:JS的继承
画的图)。
继续分析官方的_.chain
例子。这个例子拆开,写成三步。
1 | 复制代码var part1 = _.chain([1, 2, 3]); |
思考问题:reverse
本是Array.prototype
上的方法呀。为啥支持链式调用呢。
搜索reverse
,可以看到如下这段代码:
并将例子代入这段代码可得(怎么有种高中做数学题的既视感^_^):
1 | 复制代码_.chain([1,2,3]).reverse().value() |
1 | 复制代码var ArrayProto = Array.prototype; |
1 | 复制代码// Helper function to continue chaining intermediate results. |
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
提一下上面源码中的这一句,看到这句是百思不得其解。于是赶紧在github
中搜索这句加上""
双引号。表示全部搜索。
搜索到两个在官方库中的ISSUE
,大概意思就是兼容IE低版本的写法。有兴趣的可以点击去看看。
I don’t understand the meaning of this sentence.
基于流的编程
至此就算是分析完了链式调用_.chain()
和_
函数对象。这种把数据存储在实例对象{_wrapped: '', _chain: true}
中,_chain
判断是否支持链式调用,来传递给下一个函数处理。这种做法叫做 基于流的编程。
最后数据处理完,要返回这个数据怎么办呢。underscore
提供了一个value
的方法。
1 | 复制代码_.prototype.value = function(){ |
顺便提供了几个别名。toJSON
、valueOf
。
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
还提供了 toString
的方法。
1 | 复制代码_.prototype.toString = function() { |
这里的String()
和new String()
效果是一样的。
可以猜测内部实现和 _
函数对象类似。
1 | 复制代码var String = function(){ |
1 | 复制代码var chainResult = function(instance, obj) { |
细心的读者会发现chainResult
函数中的_(obj).chain()
,是怎么实现实现链式调用的呢。
而_(obj)
是返回的实例对象{_wrapped: obj}
呀。怎么会有chain()
方法,肯定有地方挂载了这个方法到_.prototype
上或者其他操作,这就是_.mixin()
。
_.mixin
挂载所有的静态方法到 _.prototype
, 也可以挂载自定义的方法
_.mixin
混入。但侵入性太强,经常容易出现覆盖之类的问题。记得之前React
有mixin
功能,Vue
也有mixin
功能。但版本迭代更新后基本都是慢慢的都不推荐或者不支持mixin
。
1 | 复制代码_.mixin = function(obj) { |
_mixin(_)
把静态方法挂载到了_.prototype
上,也就是_.prototype.chain
方法 也就是 _.chain
方法。
所以_.chain(obj)
和_(obj).chain()
效果一样,都能实现链式调用。
关于上述的链式调用,笔者画了一张图,所谓一图胜千言。
underscore.js 链式调用图解
_.mixin 挂载自定义方法
挂载自定义方法:
举个例子:
1 | 复制代码_.mixin({ |
_.functions(obj)
1 | 复制代码_.functions = _.methods = function(obj) { |
_.functions
和 _.methods
两个方法,遍历对象上的方法,放入一个数组,并且排序。返回排序后的数组。
underscore.js
究竟在_
和_.prototype
挂载了多少方法和属性
再来看下underscore.js
究竟挂载在_函数对象
上有多少静态方法和属性,和挂载_.prototype
上有多少方法和属性。
使用for in
循环一试便知。看如下代码:
1 | 复制代码var staticMethods = []; |
1 | 复制代码var prototypeMethods = []; |
根据这些,笔者又画了一张图underscore.js
原型关系图,毕竟一图胜千言。
uunderscore.js
原型关系图
整体架构概览
匿名函数自执行
1 | 复制代码(function(){ |
这样保证不污染外界环境,同时隔离外界环境,不是外界影响内部环境。
外界访问不到里面的变量和函数,里面可以访问到外界的变量,但里面定义了自己的变量,则不会访问外界的变量。
匿名函数将代码包裹在里面,防止与其他代码冲突和污染全局环境。
关于自执行函数不是很了解的读者可以参看这篇文章。
[译] JavaScript:立即执行函数表达式(IIFE)
root 处理
1 | 复制代码var root = typeof self == 'object' && self.self === self && self || |
支持浏览器环境
、node
、Web Worker
、node vm
、微信小程序
。
导出
1 | 复制代码if (typeof exports != 'undefined' && !exports.nodeType) { |
关于root处理
和导出
的这两段代码的解释,推荐看这篇文章冴羽:underscore 系列之如何写自己的 underscore,讲得真的太好了。笔者在此就不赘述了。
总之,underscore.js
作者对这些处理也不是一蹴而就的,也是慢慢积累,和其他人提ISSUE
之后不断改进的。
支持 amd
模块化规范
1 | 复制代码if (typeof define == 'function' && define.amd) { |
_.noConflict 防冲突函数
源码:
1 | 复制代码// 暂存在 root 上, 执行noConflict时再赋值回来 |
使用:
1 | 复制代码<script> |
总结
全文根据官网提供的链式调用的例子, _.chain([1, 2, 3]).reverse().value();
较为深入的调试和追踪代码,分析链式调用(_.chain()
和 _(obj).chain()
)、OOP
、基于流式编程、和_.mixin(_)
在_.prototype
挂载方法,最后整体架构分析。学习underscore.js
整体架构,利于打造属于自己的函数式编程类库。
文章分析的源码整体结构。
1 | 复制代码(function() { |
下一篇文章是学习lodash
的源码整体架构。学习 lodash 源码整体架构,打造属于自己的函数式编程类库
读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。
推荐阅读
underscore 系列之如何写自己的 underscore
笔者往期文章
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
关于
作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
若川的博客,用vuepress
重构了,阅读体验可能好些
掘金专栏,欢迎关注~
segmentfault
前端视野专栏,开通了前端视野专栏,欢迎关注~
知乎前端视野专栏,开通了前端视野专栏,欢迎关注~
语雀前端视野专栏,新增语雀专栏,欢迎关注~
github blog,相关源码和资源都放在这里,求个star
^_^~
微信公众号 若川视野
可能比较有趣的微信公众号,长按扫码关注。也可以加微信 ruochuan12
,注明来源,拉您进【前端视野交流群】。
本文使用 mdnice 排版
本文转载自: 掘金