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/bb6dc3cabae6651b94f69bbd562ff370underscore.js源码解析(五)—— 完结篇的更多相关文章
- underscore.js源码解析(四)
没看过前几篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二) underscore.js源码解析(三) underscore.js源码GitHub地 ...
- underscore.js源码解析(三)
最近工作比较忙,做不到每周两篇了,周末赶着写吧,上篇我针对一些方法进行了分析,今天继续. 没看过前两篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二 ...
- underscore.js源码解析(二)
前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文. 没看过上一篇的可以猛戳这 ...
- underscore.js源码解析(一)
一直想针对一个框架的源码好好的学习一下编程思想和技巧,提高一下自己的水平,但是看过一些框架的源码,都感觉看的莫名其妙,看不太懂,最后找到这个underscore.js由于这个比较简短,一千多行,而且读 ...
- Vue.js 源码分析(五) 基础篇 方法 methods属性详解
methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...
- underscore.js源码解析【'_'对象定义及内部函数】
(function() { // Baseline setup // -------------- // Establish the root object, `window` (`self`) in ...
- underscore.js源码解析【对象】
// Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in . ...
- underscore.js源码解析【函数】
// Function (ahem) Functions // ------------------ // Determines whether to execute a function as a ...
- underscore.js源码解析【数组】
// Array Functions // --------------- // Get the first element of an array. Passing **n** will retur ...
随机推荐
- CH4402 小Z的袜子(莫队)
描述 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只袜子从1到N编号, ...
- python使用ctypes模块下的windll.LoadLibrary报OSError: [WinError 193] % 不是有效的 Win32 应用程序
原因:python是64位的python,而windll.LoadLibrary只能由32位的python使用 参考: 64位Python调用32位DLL方法(一) 解决方法:使用32位的python ...
- PHP设置Redis key在当天有效|SCP对拷如何连接指定端口(非22端口)的远程主机
$redis->set($key,$value); $expireTime = mktime(23, 59, 59, date("m"), date("d" ...
- 一图看懂hadoop Spark On Yarn工作原理
hadoop Spark On Yarn工作原理
- goland实现函数式链式编程
先来看一段代码 package main import ( "fmt" elastic "gopkg.in/olivere/elastic.v2" ) type ...
- iOS 基于APNS消息推送原理与实现(包括JAVA后台代码)
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple ...
- 读书笔记《PHP高级程序设计、模式、框架与测试》
序言 闲来无事,下载了一些电子书,然后看书名不错<PHP高级程序设计_模式.框架与测试>,翻了一下虽然书有点老了但是讲的内容经常会碰到!给大家推荐一下,然后这里放上我的读书笔记,每日更新. ...
- MySQL事务异常
在做大屏系统的时候,遇到十分奇怪的问题,同样的代码,测试环境插入与更新操作正常,但是上了生产环境之后,插入与更新不生效, 插入数据的时候,主键会自增,但是查询表中没有数据,同样一个@Transacti ...
- XAMPP之Mysql启动失败
启动XAMPP中的Mysql出现如下: 可能的原因是本地有多个MySQL,所以要在注册表编辑器中将imagePath改成XAMPP中的mysql的地址.(打开注册表编辑器:win+R,输入regedi ...
- 数据结构与算法之数组(1)——in dart
import 'dart:math' show Random; List<int> _array; final _rnd = Random(); final _capacity = 100 ...