前言node
上一篇文章写了 jQuery总体架构,学习 jQuery 源码总体架构,打造属于本身的 js 类库git
虽然看过挺多 underscore.js分析类的文章,但总感受少点什么。这也许就是纸上得来终觉浅,绝知此事要躬行吧。因而决定本身写一篇学习 underscore.js总体架构的文章。github
本文章学习的版本是 v1.9.1。unpkg.com源码地址:https://unpkg.com/underscore@1.9.1/underscore.js面试
虽然不少人都没用过 underscore.js,但看下官方文档都应该知道如何使用。编程
从一个官方文档 _.chain简单例子看起:小程序
_.chain([1, 2, 3]).reverse().value();微信小程序
// => [3, 2, 1]数组
看例子中能够看出,这是支持链式调用。浏览器
读者也能够顺着文章思路,自行打开下载源码进行调试,这样印象更加深入。微信
_.chain 函数源码:
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
这个函数比较简单,就是传递 obj调用 _()。但返回值变量居然是 instance实例对象。添加属性 _chain赋值为 true,并返回 intance对象。但再看例子,实例对象居然能够调用 reverse方法,再调用 value方法。猜想支持 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()s
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)deleteobj[0];提一下上面源码中的这一句,看到这句是百思不得其解。因而赶忙在 github中搜索这句加上 ""双引号。表示所有搜索。
搜索到两个在官方库中的 ISSUE,大概意思就是兼容IE低版本的写法。有兴趣的能够点击去看看。
I don't understand the meaning of this sentence.
why delete obj[0]
至此就算是分析完了链式调用 _.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() 和 newString() 效果是同样的。能够猜想内部实现和 _函数对象相似。
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 混入。但侵入性太强,常常容易出现覆盖之类的问题。记得以前 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上有多少方法和属性。
使用 forin循环一试遍知。看以下代码:
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)
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
支持 浏览器环境、 node、 WebWorker、 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以后不断改进的。
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的源码总体架构。