最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题。

没看过前几篇的可以猛戳这里:

underscore.js源码解析(一)

underscore.js源码解析(二)

underscore.js源码解析(三)

underscore.js源码解析(四)

underscore.js源码GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js

本文解析的underscore.js版本是1.8.3

baseCreate

   var baseCreate = function(prototype) {
//判断参数是否是对象
if (!_.isObject(prototype)) return {};
//如果有原生的就调用原生的
if (nativeCreate) return nativeCreate(prototype);
//继承原型
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;
};

_.bind

   _.bind = restArgs(function(func, context, args) {
//如果不是函数抛异常
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
var bound = restArgs(function(callArgs) {
//调用executeBound方法,具体解释见下方
return executeBound(func, bound, context, this, args.concat(callArgs));
});
return bound;
});

executeBound

   var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
//判断boundFunc 是否在callingContext 的原型链上
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
//创建实例
var self = baseCreate(sourceFunc.prototype);
//对实例进行apply操作
var result = sourceFunc.apply(self, args);
//如果是对象则返回对象
if (_.isObject(result)) return result;
//否则返回实例本身
return self;
};

_.partial

   _.partial = restArgs(function(func, boundArgs) {
//占位符
var placeholder = _.partial.placeholder;
var bound = function() {
var position = 0, length = boundArgs.length;
var args = Array(length);
//循环遍历boundArgs
for (var i = 0; i < length; i++) {
//判断是否是占位符,如果是就把arguments里的第一个放进去(按顺序以此类推),
//如果不是占位符就正常把boundArgs里的数据再拷贝一份到args中
args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
}
//循环遍历完boundArgs,就是把剩下的数据放入到args当中,这里调用executeBound,executeBound的分析可以看上面
while (position < arguments.length) args.push(arguments[position++]);
return executeBound(func, bound, this, this, args);
};
return bound;
});

_.bindAll

   _.bindAll = restArgs(function(obj, keys) {
keys = flatten(keys, false, false);
var index = keys.length;
//如果没有 function names抛异常
if (index < 1) throw new Error('bindAll must be passed function names');
while (index--) {
var key = keys[index];
//调用bind方法进行绑定
obj[key] = _.bind(obj[key], obj);
}
});

多个方法绑定到对象上

_.memoize

   _.memoize = function(func, hasher) {
var memoize = function(key) {
//缓存值
var cache = memoize.cache;
//是否使用hashFunction,如果使用就把hashFunction的返回值作为缓存的key值
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
//如果没有就做一个缓存的操作
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
//最后返回缓存值
return cache[address];
};
memoize.cache = {};
return memoize;
};

作用是缓存函数的计算结果,再做里面有重复运算的情况优化效果比较明显

_.delay

   _.delay = restArgs(function(func, wait, args) {
return setTimeout(function() {
return func.apply(null, args);
}, wait);
});

就是对setTimeout的封装,一目了然就不做过多解释了

_.defer

 _.defer = _.partial(_.delay, _, 1);

就是让这段程序最后执行,也是调用setTimeout来实现的,这里“_”是函数参数的占位,1是时间1毫秒。不懂的可以去看看setTimeout的机制,如果这里再展开的话篇幅过长,有时间也可以写一篇setTimeout的文章

_.throttle

   _.throttle = function(func, wait, options) {
var timeout, context, args, result;
//previous是缓存的上一次执行的时间点,默认为0
var previous = 0;
//判断是否有配置参数
if (!options) options = {}; var later = function() {
previous = options.leading === false ? 0 : _.now();
//清除timeout
timeout = null;
//储存函数执行的结果
result = func.apply(context, args);
if (!timeout) context = args = null;
}; var throttled = function() {
//当前时间
var now = _.now();
if (!previous && options.leading === false) previous = now;
//wait是setTimeout延迟的时间
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;
} 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;
};

_.throttle的作用是控制函数的执行频率,第一次执行的时候previous默认为零,那么remaining就是负数,没有定时器,之后当remaining大于0时,启动定时器,当定时器的时间到的时候,执行定时器里面的函数,并且会请一次timeout,remaining此时大于零并且timeout为空,则进入else if再次生成一个setTimeout。remaining > wait也就意味着now < previous,这是为了规避用户改变系统是简单的情况,这时候需要清除timeout的操作。

_.debounce

   _.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) {
//判断是否立即调用
var callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
if (callNow) {
//如果立即调用则,立即执行函数
timeout = setTimeout(later, wait);
result = func.apply(this, args);
} else if (!immediate) {
//如果本次调用时,上一个定时器没有执行完,将再生成一个定时器
timeout = _.delay(later, wait, this, args);
} return result;
}); debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
}; return debounced;
};

_.debounce也是函数节流,但是与throttle不同的是debounce中两个函数的时间间隔不能小于wait,这样的话定时器就会被重新创建

_.wrap

   _.wrap = function(func, wrapper) {
return _.partial(wrapper, func);
};

作用就是把func当做参数传给wrapper执行,_.partial前文介绍过,就是给函数设置一些默认的参数

_.compose

   _.compose = function() {
var args = arguments;
var start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
//从后往前调用
while (i--) result = args[i].call(this, result);
return result;
};
};

_.compose的作用就是组合复合函数,结构就是从最后一个函数开始执行,然后返回结果给前一个函数调用,直到第一个。

_.after

   _.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};

原理很简单,就是只有调用到最后一次的时候才开始执行里面的函数

_.before

   _.before = function(times, func) {
var memo;
return function() {
if (--times > 0) {
//正常调用,记录返回值
memo = func.apply(this, arguments);
}
//最后一次调用时,清空fun
if (times <= 1) func = null;
return memo;
};
};

_.before的作用是限制函数的调用次数,最后一次调用清空fun,返回上一次调用的结果

_.once

   _.once = _.partial(_.before, 2);

_.once调用了_.before并且times参数为2,说明无论调用几次,只返回第一次的调用结果

_.mapObject

   _.mapObject = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
var keys = _.keys(obj),
length = keys.length,
results = {};
for (var index = 0; index < length; index++) {
var currentKey = keys[index];
results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
};

_.mapObject跟map类似,只不过它最后返回的是对象

_.pairs

   _.pairs = function(obj) {
var keys = _.keys(obj);
var length = keys.length;
var pairs = Array(length);
for (var i = 0; i < length; i++) {
pairs[i] = [keys[i], obj[keys[i]]];
}
return pairs;
};

_.pairs的结构也很简单,就是把对象转化为数组

_.invert

   _.invert = function(obj) {
var result = {};
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
result[obj[keys[i]]] = keys[i];
}
return result;
};

结构也是很清晰,就是一个翻转对象的过程,将对象的键和值互换位置

_.functions

   _.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};

就是获取对象的所有方法名,然后存在数组当中

_.pick

   _.pick = restArgs(function(obj, keys) {
var result = {}, iteratee = keys[0];
//如果没有传入obj,则返回空
if (obj == null) return result;
//判断keys参数里是否传的是函数
if (_.isFunction(iteratee)) {
如果是函数,则调用函数进行上下文this的绑定
if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
keys = _.allKeys(obj);
} else {
//如果不是函数,则为所需的属性
iteratee = keyInObj;
keys = flatten(keys, false, false);
obj = Object(obj);
}
for (var i = 0, length = keys.length; i < length; i++) {
var key = keys[i];
var value = obj[key];
if (iteratee(value, key, obj)) result[key] = value;
}
return result;
});

作用是过滤出所需的键值对,对参数是属性的和函数的情况分别处理

_.omit

   _.omit = restArgs(function(obj, keys) {
var iteratee = keys[0], context;
if (_.isFunction(iteratee)) {
//这里一个取反的操作
iteratee = _.negate(iteratee);
if (keys.length > 1) context = keys[1];
} else {
keys = _.map(flatten(keys, false, false), String);
iteratee = function(value, key) {
//不存在的情况返回true
return !_.contains(keys, key);
};
}
//最后调用pick()
return _.pick(obj, iteratee, context);
});

_.omit相比较_.pick是一种相反的操作,作用是保留标记以外的对象

_.create

   _.create = function(prototype, props) {
//继承原型
var result = baseCreate(prototype);
//属性拷贝的操作
if (props) _.extendOwn(result, props);
return result;
};

模拟Object.create方法

_.clone

   _.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

对象浅拷贝,如果是数组就调用slice,不是数组就调用_.extend

_.isMatch

   _.isMatch = function(object, attrs) {
var keys = _.keys(attrs), length = keys.length;
if (object == null) return !length;
//防止不是对象
var obj = Object(object);
for (var i = 0; i < length; i++) {
var key = keys[i];
//如果对象属性不在obj中或者不在obj中
if (attrs[key] !== obj[key] || !(key in obj)) return false;
}
return true;
};

判断后者的对象属性是否全在前者的对象当中

eq

  eq = function(a, b, aStack, bStack) {
//虽然0 === -0成立,但是1 / 0 == 1 / -0 是不成立的,因为1 / 0值为Infinity, 1 / -0值为-Infinity, 而Infinity不等于-Infinity
if (a === b) return a !== 0 || 1 / a === 1 / b;
//null == undefined
if (a == null || b == null) return a === b;
//对NaN情况的判断,因为NaN!=NaN,所以a !== a说明a是NaN,如果b !== b为true,那么说明b是NaN,a和b相等,b !== b为false,说明b不是NaN,那么a和b不等
if (a !== a) return b !== b;
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
return deepEq(a, b, aStack, bStack);
};

deepEq

   deepEq = function(a, b, aStack, bStack) {
// 如果是underscore封装的对象,则通过_.wrapped中获取本身数据再进行对比
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// 对两者的数据类型进行比较
var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {
case '[object RegExp]':
case '[object String]':
// 正则转化字符串
return '' + a === '' + b;
case '[object Number]':
// 对NaN情况的判断,跟eq里的判断一样,只不过多了转化数字这一步
if (+a !== +a) return +b !== +b;
// 如果不是NaN,那就要判断0的情况了,也是跟eq里面的判断同理
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
//日期和布尔值转化为数字来比较,日期转化为数字是毫秒数
return +a === +b;
} var areArrays = className === '[object Array]';
if (!areArrays) {
//如果不是数组,只要有一个不是object类型就不等
if (typeof a != 'object' || typeof b != 'object') return false; var aCtor = a.constructor, bCtor = b.constructor;
//不同的构造函数是不等的,不同frames的object和Array是相等的
if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
_.isFunction(bCtor) && bCtor instanceof bCtor)
&& ('constructor' in a && 'constructor' in b)) {
return false;
}
} aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
while (length--) {
// 对嵌套结构的做判断
if (aStack[length] === a) return bStack[length] === b;
} // 将a和b放入栈中
aStack.push(a);
bStack.push(b); // 对数组的判断处理
if (areArrays) {
length = a.length;
//如果长度不等,那么肯定不等
if (length !== b.length) return false;
// 递归比较每一个元素
while (length--) {
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
} else {
//如果是对象
var keys = _.keys(a), key;
length = keys.length;
// 相比较亮两个对象的属性数量是否相等
if (_.keys(b).length !== length) return false;
while (length--) {
//递归比较每个属性是否相等
key = keys[length];
if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
// 移除栈里的元素
aStack.pop();
bStack.pop();
return true;
};

_.isEmpty

   _.isEmpty = function(obj) {
if (obj == null) return true;
if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
return _.keys(obj).length === 0;
};

就是一个判断为空的函数,结构很简单

_.isElement

   _.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};

判断是都是DOM元素

_.times

   _.times = function(n, iteratee, context) {
var accum = Array(Math.max(0, n));
iteratee = optimizeCb(iteratee, context, 1);
for (var i = 0; i < n; i++) accum[i] = iteratee(i);
return accum;
};

调用迭代函数n次,最后结果返回一个数组

_.template

   _.template = function(text, settings, oldSettings) {
//如果没有第二个参数,就将第三个参数赋值给第二个
if (!settings && oldSettings) settings = oldSettings;
//这里_.default函数前面介绍过填充属性为undefined的属性
settings = _.defaults({}, settings, _.templateSettings); // 定义正则表达式,将settings里面的三个正则组合在一起,这里'|$'是为了让replace里面的函数多执行一遍
var matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g'); var index = 0;
var source = "__p+='";
//拼接字符串
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
index = offset + match.length;
//针对不同的情况进行拼接
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
} return match;
});
//下面是一个模板预编译的处理,主要用于调试
source += "';\n"; if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n'; var render;
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
//创建templete函数
var template = function(data) {
return render.call(this, data, _);
};
//设置source属性
var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}'; return template;
};

_.template就是一个模板函数,核心的部分还是前半段字符串拼接的过程(17-38行)。

首先先解释一下参数text是模板字符串,settings是正则匹配的规则,escape、interpolate、evaluate和variable

   _.templateSettings = {
evaluate: /<%([\s\S]+?)%>/g,
interpolate: /<%=([\s\S]+?)%>/g,
escape: /<%-([\s\S]+?)%>/g
};

evaluate是用来执行任意的JavaScript代码,interpolate是用来插入变量的,escape是HTML转义的

里面有个noMatch,他是为了避免settings中缺少属性的情况

 var noMatch = /(.)^/;

17行里offset是用来记录匹配当前位置的,剩下的主要就是走21-27行了,插入值就走23-24行判断,如果遇到一些需要js判断转换的数据就走25-26行判断,最后匹配$也是为了再执行一遍将最后面的html拼接进字符串。

其中18行中的escapeRegExp和escapeChar就是用来转化一些特殊字符的

   var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
}; var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; var escapeChar = function(match) {
return '\\' + escapes[match];
};

小结

到这里underscore.js就都分析完了,断断续续用了一个月的时间,在读这些函数方法的时候收获很多,也发现了一些以前自己理解不全面的地方,对自己也是个检验,就拿上面的template来说,就发现了自己正则方面的掌握还不够,在分析方法时学习了一些这些好的编程思想。

下一篇可能是对定时器的分析,到时候再看自己的研究效果吧。

夜已深,去睡觉了...

感谢大家的观看,也希望能够和大家互相交流学习,有什么分析的不对的地方欢迎大家批评指出

参考资料

http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370

underscore.js源码解析(五)—— 完结篇的更多相关文章

  1. underscore.js源码解析(四)

    没看过前几篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二) underscore.js源码解析(三) underscore.js源码GitHub地 ...

  2. underscore.js源码解析(三)

    最近工作比较忙,做不到每周两篇了,周末赶着写吧,上篇我针对一些方法进行了分析,今天继续. 没看过前两篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二 ...

  3. underscore.js源码解析(二)

    前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文. 没看过上一篇的可以猛戳这 ...

  4. underscore.js源码解析(一)

    一直想针对一个框架的源码好好的学习一下编程思想和技巧,提高一下自己的水平,但是看过一些框架的源码,都感觉看的莫名其妙,看不太懂,最后找到这个underscore.js由于这个比较简短,一千多行,而且读 ...

  5. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

  6. underscore.js源码解析【'_'对象定义及内部函数】

    (function() { // Baseline setup // -------------- // Establish the root object, `window` (`self`) in ...

  7. underscore.js源码解析【对象】

    // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in . ...

  8. underscore.js源码解析【函数】

    // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a ...

  9. underscore.js源码解析【数组】

    // Array Functions // --------------- // Get the first element of an array. Passing **n** will retur ...

随机推荐

  1. 前端调用接口得到的数据跟postman跑出来的数据里数字部份不相等

    昨天碰到这样一个场景,调用后端接口返回的数据发现所有数据都是正常的,只有一个商品ID的最后两位是错的,每一个商品都是,导致无法进行商品的上下架和删除, 经过查资料发现: 浏览器解析数字的坑,一旦超出一 ...

  2. Web前端---HTTP协议

    目录 HTTP协议 一.http协议概述 二.http请求报文 1.GET请求 2.POST请求 三.http响应报文 1.响应报文内容 2.状态码(Status Code) HTTP协议 一.htt ...

  3. 使用xampp发现php的date()函数与本地相差7个小时

    具体方法: 1. 打开php.ini 2. 搜索timezone 3. 修改为PRC 4. 回车键 5. 修改为PRC 6. 完成 没想到这么一个小问题也是一个大坑,在网上找了半天基本都是说要修改这个 ...

  4. 自定义loading效果

    结合Font Awesome字体图标自定义loading效果 Font Awesome字体图标地址:http://www.fontawesome.com.cn/faicons/ 使用javascrip ...

  5. Ubuntu 16.04 64位安装YouCompleteMe

    之前记录在OneNote上感觉有点乱,而且不适合保存shell,这次重新安装又出问题了,干脆写篇博客记录. 从零开始 1.git(用来下载vim和相关插件) sudo apt-get install ...

  6. Redis笔记 -- 链表和链表节点的API函数(三)

    链表和链表节点API 函数 作用 时间复杂度 listSetDupMethod 将给定的函数设置为链表的节点值复制函数 复制函数可以通过链表的dup属性直接获得,O(1) listGetDupMeth ...

  7. 树莓派 ubuntu16.04 安装SSH 配置SSH 开机自启SSH

    入手个树莓派3B 装了 ubuntu 16.04 需要用到SSH 记录下 0.先获得树莓派IP 树莓派 使用网线连接路由器和树莓派 在路由器设置页面(一般是192.168.1.1具体看路由器的型号和设 ...

  8. 爬虫常用的 urllib 库知识点

    urllib 库 urllib 库是 Python 中一个最基本的网络请求库.它可以模仿浏览器的行为向指定的服务器发送请求,同时可以保存服务器返回的数据. urlopen() 在 Python3 的 ...

  9. 详解LeetCode 137. Single Number II

    Given an array of integers, every element appears three times except for one, which appears exactly ...

  10. 【转】Excel-VBA操作文件四大方法之三

    三.利用FileSystemObject对象来处理文件 FileSystemObject对象模型,是微软提供的专门用来访问计算机文件系统的,具有大量的属性.方法和事件.其使用面向对象的“object. ...