本文基于
underscore
v1.8.3版本
源头
一直想学习一下类库的源码,jQuery
刚刚看到选择器那块,直接被那一大块正则搞懵逼了。经过同事的推荐,选择了underscore
来作为类库研究的起点。
闭包
所有函数都在一个闭包内,避免污染全局变量,这没什么特殊的,略过。。。
(function() { ... }());
全局对象的获取
先看下面一段代码:
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this;
self
是什么鬼?global
跟this
都能够猜出来是全局变量,这个self
从哪里冒出来的?
第一眼看到这样的代码很困惑,感觉压根没有头绪。但是如果你打开chrome
的控制台,神奇的事情发生了
其实查看源码的注释,我们也能看出来这段代码的作用:在不一样的环境里面获取当前全局对象this
self | window
:浏览器global
:服务端this
:某些虚拟机
为了压缩所做的原型赋值
源码中有将对象的原型链赋值给一个变量的做法:
var ArrayProto = Array.prototype, ObjProto = Object.prototype;var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
一开始我并没明白这么做的优势,代码不都一样吗?
参考注释并且上网查资料才知道原因:为了压缩
举个例子,Array.prototype
是没有办法经过压缩的,Array
,prototype
这些,如果改了,浏览器就无法识别这些字段了。
但经过类似上面代码的处理,ObjProto
经过压缩就能变成变量a
,那么原来的代码就会变成a.xxx
。
我们平常写的代码也可以进行类似上面的处理,只要代码的复用超过两次,就可以考虑将其赋值给一个变量了。
this
值统一处理
this
在类库中的应用很广泛,undersocre
采用了一个内部函数来处理this
:
var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; // The 2-parameter case has been omitted only because no current consumers // made use of it. case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; };
注意到上面的case
语句没有2
的情况,看其注释基本就能明白,这是因为没有使用到2
的情况。
上面函数的最后一个参数argCount
是用来指定参数个数:
接受单值的情况
已取消
迭代器函数
reduce
函数
callback
的统一处理
var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); };
cb
就是callback
的简写,看函数的注释的意思是:内部函数,用来生成可应用于集合内每个元素的回调函数,返回预期的结果,具体应用向下看。
iteratee
什么鬼?
_.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); };
结合上面的cb
函数,貌似可以看到每次调用cb
函数时都会判断一次_.iteratee
是否等于builtinIteratee
。
如果不等于则调用_.iteratee
函数,让_.iteratee = builtinIteratee
,再继续执行cb
函数。
结合注释,猜测这个函数的作用应该是防止用户自己定义iteratee
函数。
restArgs
又一个基础函数
var restArgs = function(func, startIndex) { startIndex = startIndex == null ? func.length - 1 : +startIndex; return function() { var length = Math.max(arguments.length - startIndex, 0), rest = Array(length), index = 0; for (; index < length; index++) { rest[index] = arguments[index + startIndex]; } switch (startIndex) { case 0: return func.call(this, rest); case 1: return func.call(this, arguments[0], rest); case 2: return func.call(this, arguments[0], arguments[1], rest); } var args = Array(startIndex + 1); for (index = 0; index < startIndex; index++) { args[index] = arguments[index]; } args[startIndex] = rest; return func.apply(this, args); }; };
这个函数作用就类似ES6
里面的rest params
,这个函数主要是在官网分类里面的collections
用到,例如:invoke
。
主要原理是利用回调函数来处理调用方法传入的参数。
创建继承函数
var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; // 创建 result 之后清空 Ctor 的原型链,防止 全局变量 Ctor 的原型链污染 Ctor.prototype = null; return result; };
主要原理就是利用 Ctor 做一个中介,创建继承函数并返回后再清空Ctor的原型链,防止原型链污染
取对象的属性值
var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; };
这个方法浅显易懂,如果传入的object
为null
,则返回 undefined
,否则返回属性值。
其它的全局变量
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;var getLength = property('length');var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; };
主要是帮助collection
的方法来判定某个变量是否为collection
。
后记
看完这一段,感觉依旧有许多疑问。主要是因为这些全局定义的变量的使用场景没有深究,更直白一些,就是没有按照代码的线索专研下去。希望在接下来的主要API
的分析中能够在好好回顾上面的那些函数以及变量