underscore源码分析
Underscore一个JavaScript实用库,提供了一整套函数式编程的实用功能,前前后后总共100+个方法,但是没有扩展任何JavaScript内置对象。
underscore整体在一个闭包函数中,加载underscore类库时,执行代码自适应不同的JS环境。
1 | (function(){ |
仅供内部使用的函数都以驼峰命名法进行命名,供外部使用的函数都挂载在_
上。当然能供外部使用的函数内部也有在使用。underscore主要有四个部分使用感到精彩,高内聚低耦合的内部方法、依据环境生成方法、特殊方法的实现、其他。
高内聚低耦合的内部方法
optimizeCb返回一个绑定了上下文的方法
1 | var optimizeCb = function(func, context, argCount) { |
cb 返回一个回调函数1
2
3
4
5
6
7
8
9
10
11
12var 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) && !_.isArray(value)) return _.matcher(value);
// 生成一个获取对象属性的函数
return _.property(value);
};
restArgs包装一个函数,将函数多余的参数包装为一个数组传递给最后一个参数,减少了每个函数中都是用arguments获取多余参数的麻烦1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var restArgs = function(func, startIndex) {
// func.length获取定义的形式参数个数
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);
};
};
在对外函数中的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 判断元素是否全部满足
_.every = _.all = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
};
// 延迟函数执行
_.delay = restArgs(function(func, wait, args) {
return setTimeout(function() {
return func.apply(null, args);
}, wait);
});
依据环境生成代码
生成判断函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) === '[object ' + name + ']';
};
});
//测试判断函数是否有效,无效则用新方法代替
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return _.has(obj, 'callee');
};
}
var nodelist = root.document && root.document.childNodes;
//浏览器特性监测,当前浏览器typeof,不将/./、NodeList视为函数,Int8Array视为对象,则可以使用typeof进行函数的判定
if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
}
生成链式函数调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 生成_对象,(只是将普通的对象包装为{_wrapped:obj}对象
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// 开启链式调用
_.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(_);
特殊方法实现
.partial 顺序填充一些参数给函数,并返回预处理后的函数使用``作为占位符
1 | var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { |
_.throttle 节流函数返回一个函数,在一段时间之内只调用一次,用于处理像onresize这样会频繁触发的事件的处理函数。
1 | _.throttle = function(func, wait, options) { |
_.debounced 消抖函数,连续触发的情况下只会执行一次,用于作为像onresize这样会频繁触发的事件的处理函数。
1 | //wait延迟执行的时间,immediate是否立即执行 |
_.negate 生成一个返回布尔类型相反的函数1
2
3
4
5_.negate = function(predicate) {
return function() {
return !predicate.apply(this, arguments);
};
};
其他
+0 不等于 -0的比较
if (a === b) return a !== 0 || 1 / a === 1 / b;
NaN比较
typeof NaN == ‘number’ && NaN != NaN
Symbol相等的比较
Symbol.prototype.valueOf.call(a) === Symbol.prototype.valueOf.call(b)
匹配所有文字/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
函数定义时定义的参数的个数
func.length
去if,获得非undefined值的方式1
2
3(keys || obj).length;
keys = !isArrayLike(obj) && _.keys(obj);
根据dir的值,进行正反方向的迭代1
2
3
4index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
//...
}