Fork me on GitHub

underscore源码分析

underscore源码分析

Underscore一个JavaScript实用库,提供了一整套函数式编程的实用功能,前前后后总共100+个方法,但是没有扩展任何JavaScript内置对象。

underscore整体在一个闭包函数中,加载underscore类库时,执行代码自适应不同的JS环境。

1
2
3
4
5
6
7
8
(function(){
//找出当前执行的环境的全局变量
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
//...
}())

仅供内部使用的函数都以驼峰命名法进行命名,供外部使用的函数都挂载在_上。当然能供外部使用的函数内部也有在使用。underscore主要有四个部分使用感到精彩,高内聚低耦合的内部方法、依据环境生成方法、特殊方法的实现、其他。

高内聚低耦合的内部方法

optimizeCb返回一个绑定了上下文的方法

1
2
3
4
5
6
7
8
9
10
11
12
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount) {
case 1: return function(value) {
return func.call(context, value);
};
//省略...
}
return function() {
return func.apply(context, arguments);
};
};

cb 返回一个回调函数

1
2
3
4
5
6
7
8
9
10
11
12
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) && !_.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
24
var 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
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
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
var self = baseCreate(sourceFunc.prototype);
var result = sourceFunc.apply(self, args);
if (_.isObject(result)) return result;
return self;
};
//根据预定义参数返回代理函数
_.partial = restArgs(function(func, boundArgs) {
var placeholder = _.partial.placeholder;
var bound = function() {
var position = 0, length = boundArgs.length;
var args = Array(length);
for (var i = 0; i < length; i++) {
//组装参数,如果预填充的参数为占位符,则使用调用时的参数来补齐。
args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
}
//添加剩余的参数
while (position < arguments.length) args.push(arguments[position++]);
//执行原方法
return executeBound(func, bound, this, this, args);
};
return bound;
});
//占位符
_.partial.placeholder = _;

_.throttle 节流函数返回一个函数,在一段时间之内只调用一次,用于处理像onresize这样会频繁触发的事件的处理函数。

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
32
33
34
35
36
37
38
39
40
41
42
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
// 最后一次是否执行
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};

var throttled = function() {
var now = _.now();
//leading参数表示是否是以调用函数的时间做时间间隔。
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
//trailing参数表示是否事件结束仍进行调用。
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
//取消节流函数
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};

return throttled;
};

_.debounced 消抖函数,连续触发的情况下只会执行一次,用于作为像onresize这样会频繁触发的事件的处理函数。

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
//wait延迟执行的时间,immediate是否立即执行
_.debounce = function(func, wait, immediate) {
var timeout, result;

var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};

var debounced = restArgs(function(args) {
//含有timeout,即连续调用,不执行
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
//有立即执行参数时,第一次调用马上执行。
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}

return result;
});
//取消执行函数
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};

return debounced;
};

_.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
4
index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
//...
}

坚持原创技术分享,您的支持将鼓励我继续创作!