上一篇文章写了jQuery总体架构
,学习 jQuery 源码总体架构,打造属于本身的 js 类库前端
虽然看过挺多underscore.js
分析类的文章,但总感受少点什么。这也许就是纸上得来终觉浅,绝知此事要躬行吧。因而决定本身写一篇学习underscore.js
总体架构的文章。node
本文章学习的版本是v1.9.1
。unpkg.com
源码地址:https://unpkg.com/underscore@...git
虽然不少人都没用过underscore.js
,但看下官方文档都应该知道如何使用。github
从一个官方文档_.chain
简单例子看起:面试
_.chain([1, 2, 3]).reverse().value(); // => [3, 2, 1]
看例子中能够看出,这是支持链式调用。编程
读者也能够顺着文章思路,自行打开下载源码进行调试,这样印象更加深入。小程序
_.chain
函数源码:segmentfault
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; };
这个函数比较简单,就是传递obj
调用_()
。但返回值变量居然是instance
实例对象。添加属性_chain
赋值为true
,并返回intance
对象。但再看例子,实例对象居然能够调用reverse
方法,再调用value
方法。猜想支持OOP
(面向对象)调用。微信小程序
带着问题,笔者看了下定义 _
函数对象的代码。api
_
函数对象 支持OOP
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
若是参数obj
已是_
的实例了,则返回obj
。
若是this
不是_
的实例,则手动 new _(obj)
;
再次new
调用时,把obj
对象赋值给_wrapped
这个属性。
也就是说最后获得的实例对象是这样的结构
`{
_wrapped: '参数obj',
}`
它的原型_(obj).__proto__
是 _.prototype
;
若是对这块不熟悉的读者,能够看下如下这张图(以前写面试官问:JS的继承
画的图)。
继续分析官方的_.chain
例子。这个例子拆开,写成三步。
var part1 = _.chain([1, 2, 3]); var part2 = part1.reverse(); var part3 = part2.value(); // 没有后续part1.reverse()操做的状况下 console.log(part1); // {__wrapped: [1, 2, 3], _chain: true} console.log(part2); // {__wrapped: [3, 2, 1], _chain: true} console.log(part3); // [3, 2, 1]
思考问题:reverse
本是Array.prototype
上的方法呀。为啥支持链式调用呢。
搜索reverse
,能够看到以下这段代码:
并将例子代入这段代码可得(怎么有种高中作数学题的既视感^_^):
_.chain([1,2,3]).reverse().value()
var ArrayProto = Array.prototype; // 遍历 数组 Array.prototype 的这些方法,赋值到 _.prototype 上 _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { // 这里的`method`是 reverse 函数 var method = ArrayProto[name]; _.prototype[name] = function() { // 这里的obj 就是数组 [1, 2, 3] var obj = this._wrapped; // arguments 是参数集合,指定reverse 的this指向为obj,参数为arguments, 并执行这个函数函数。执行后 obj 则是 [3, 2, 1] method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; // 重点在于这里 chainResult 函数。 return chainResult(this, obj); }; });
// Helper function to continue chaining intermediate results. var chainResult = function(instance, obj) { // 若是实例中有_chain 为 true 这个属性,则返回实例 支持链式调用的实例对象 { _chain: true, this._wrapped: [3, 2, 1] },不然直接返回这个对象[3, 2, 1]。 return instance._chain ? _(obj).chain() : obj; };
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
提一下上面源码中的这一句,看到这句是百思不得其解。因而赶忙在github
中搜索这句加上""
双引号。表示所有搜索。
搜索到两个在官方库中的ISSUE
,大概意思就是兼容IE低版本的写法。有兴趣的能够点击去看看。
I don't understand the meaning of this sentence.
[why delete obj[0]](https://github.com/jashkenas/...
至此就算是分析完了链式调用_.chain()
和_
函数对象。这种把数据存储在实例对象{_wrapped: '', _chain: true}
中,_chain
判断是否支持链式调用,来传递给下一个函数处理。这种作法叫作 基于流的编程。
最后数据处理完,要返回这个数据怎么办呢。underscore
提供了一个value
的方法。
_.prototype.value = function(){ return this._wrapped; }
顺便提供了几个别名。toJSON
、valueOf
。
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
还提供了 toString
的方法。
_.prototype.toString = function() { return String(this._wrapped); };
这里的String()
和new String()
效果是同样的。
能够猜想内部实现和 _
函数对象相似。
var String = function(){ if(!(this instanceOf String)) return new String(obj); }
var chainResult = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; };
细心的读者会发现chainResult
函数中的_(obj).chain()
,是怎么实现实现链式调用的呢。
而_(obj)
是返回的实例对象{_wrapped: obj}
呀。怎么会有chain()
方法,确定有地方挂载了这个方法到_.prototype
上或者其余操做,这就是_.mixin()
。
_.mixin
挂载全部的静态方法到 _.prototype
, 也能够挂载自定义的方法_.mixin
混入。但侵入性太强,常常容易出现覆盖之类的问题。记得以前React
有mixin
功能,Vue
也有mixin
功能。但版本迭代更新后基本都是慢慢的都不推荐或者不支持mixin
。
_.mixin = function(obj) { // 遍历对象上的全部方法 _.each(_.functions(obj), function(name) { // 好比 chain, obj['chain'] 函数,自定义的,则赋值到_[name] 上,func 就是该函数。也就是说自定义的方法,不只_函数对象上有,并且`_.prototype`上也有 var func = _[name] = obj[name]; _.prototype[name] = function() { // 处理的数据对象 var args = [this._wrapped]; // 处理的数据对象 和 arguments 结合 push.apply(args, arguments); // 链式调用 chain.apply(_, args) 参数又被加上了 _chain属性,支持链式调用。 // _.chain = function(obj) { // var instance = _(obj); // instance._chain = true; // return instance; }; return chainResult(this, func.apply(_, args)); }; }); // 最终返回 _ 函数对象。 return _; }; _.mixin(_);
_mixin(_)
把静态方法挂载到了_.prototype
上,也就是_.prototype.chain
方法 也就是 _.chain
方法。
因此_.chain(obj)
和_(obj).chain()
效果同样,都能实现链式调用。
关于上述的链式调用,笔者画了一张图,所谓一图胜千言。
挂载自定义方法:
举个例子:
_.mixin({ log: function(){ console.log('哎呀,我被调用了'); } }) _.log() // 哎呀,我被调用了 _().log() // 哎呀,我被调用了
_.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); };
_.functions
和 _.methods
两个方法,遍历对象上的方法,放入一个数组,而且排序。返回排序后的数组。
underscore.js
究竟在_
和_.prototype
挂载了多少方法和属性再来看下underscore.js
究竟挂载在_函数对象
上有多少静态方法和属性,和挂载_.prototype
上有多少方法和属性。
使用for in
循环一试便知。看以下代码:
var staticMethods = []; var staticProperty = []; for(var name in _){ if(typeof _[name] === 'function'){ staticMethods.push(name); } else{ staticProperty.push(name); } } console.log(staticProperty); // ["VERSION", "templateSettings"] 两个 console.log(staticMethods); // ["after", "all", "allKeys", "any", "assign", ...] 138个
var prototypeMethods = []; var prototypeProperty = []; for(var name in _.prototype){ if(typeof _.prototype[name] === 'function'){ prototypeMethods.push(name); } else{ prototypeProperty.push(name); } } console.log(prototypeProperty); // [] console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 152个
根据这些,笔者又画了一张图underscore.js
原型关系图,毕竟一图胜千言。
(function(){ }());
这样保证不污染外界环境,同时隔离外界环境,不是外界影响内部环境。
外界访问不到里面的变量和函数,里面能够访问到外界的变量,但里面定义了本身的变量,则不会访问外界的变量。
匿名函数将代码包裹在里面,防止与其余代码冲突和污染全局环境。
关于自执行函数不是很了解的读者能够参看这篇文章。
[[译] JavaScript:当即执行函数表达式(IIFE)](https://segmentfault.com/a/11...
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {};
支持浏览器环境
、node
、Web Worker
、node vm
、微信小程序
。
if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }
关于root处理
和导出
的这两段代码的解释,推荐看这篇文章冴羽:underscore 系列之如何写本身的 underscore,讲得真的太好了。笔者在此就不赘述了。
总之,underscore.js
做者对这些处理也不是一蹴而就的,也是慢慢积累,和其余人提ISSUE
以后不断改进的。
amd
模块化规范if (typeof define == 'function' && define.amd) { define('underscore', [], function() { return _; }); }
源码:
// 暂存在 root 上, 执行noConflict时再赋值回来 var previousUnderscore = root._; _.noConflict = function() { root._ = previousUnderscore; return this; };
使用:
<script> var _ = '我就是我,不同的烟火,其余可不要覆盖我呀'; </script> <script src="https://unpkg.com/underscore@1.9.1/underscore.js"> </script> <script> var underscore = _.noConflict(); console.log(_); // '我就是我,不同的烟火,其余可不要覆盖我呀' underscore.isArray([]) // true </script>
全文根据官网提供的链式调用的例子, _.chain([1, 2, 3]).reverse().value();
较为深刻的调试和追踪代码,分析链式调用(_.chain()
和 _(obj).chain()
)、OOP
、基于流式编程、和_.mixin(_)
在_.prototype
挂载方法,最后总体架构分析。学习Underscore.js
总体架构,利于打造属于本身的函数式编程类库。
文章分析的源码总体结构。
(function() { var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {}; var previousUnderscore = root._; var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } _.VERSION = '1.9.1'; _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; var chainResult = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return chainResult(this, func.apply(_, args)); }; }); return _; }; _.mixin(_); _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return chainResult(this, obj); }; }); _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return chainResult(this, method.apply(this._wrapped, arguments)); }; }); _.prototype.value = function() { return this._wrapped; }; _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return String(this._wrapped); }; if (typeof define == 'function' && define.amd) { define('underscore', [], function() { return _; }); } }());
下一篇文章但是学习lodash
的源码总体架构。学习 lodash 源码总体架构,打造属于本身的函数式编程类库
读者发现有不妥或可改善之处,欢迎评论指出。另外以为写得不错,能够点赞、评论、转发,也是对笔者的一种支持。
underscorejs.org 官网
undersercore-analysis
underscore 系列之如何写本身的 underscore
学习 jQuery 源码总体架构,打造属于本身的 js 类库
面试官问:JS的继承
面试官问:JS的this指向
面试官问:可否模拟实现JS的call和apply方法
面试官问:可否模拟实现JS的bind方法
面试官问:可否模拟实现JS的new操做符
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
做者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,惟善学。
我的博客 https://lxchuan12.github.io
github blog,相关源码和资源都放在这里,求个star
^_^~
可能比较有趣的微信公众号,长按扫码关注。也能够加微信 lxchuan12
,注明来源,拉您进【前端视野交流群】。