underscore源码经典--收藏
underscore 源码版本 1.8.2
起因
很多人向我推荐研究js,可以看看一些第三方js类库的源码,而源码之中最好解读也最简短的就是underscore,它也是我平常比较喜欢的一个库,因为它性价比高:体积小、能力强。打开一看,才1000多行,试着读了一下,确实很值得一看,所以对精彩部分做了一下整理。
闭包
整个函数在一个闭包中,避免污染全局变量。通过传入this(其实就是window对象)来改变函数的作用域。和jquery的自执行函数其实是异曲同工之妙。这种传入全局变量的方式一方面有利于代码阅读,另一方面方便压缩。
underscore写法:
(function(){...
}.call(this));
jquery写法:
(function(window, undefined) {...
})(window);
原型赋值
18 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
Array,Object,Function这些本质都是函数,获取函数原型属性prototype也是为了便于压缩。简单解释一下,如果代码中要扩展属性,可能这样写
Object.prototype.xxx = ...
而这种代码是不可压缩的,Object
,prototype
这些名字改了浏览器就不认得了。
但是上面的代码中创建了ObjProto
之后,源生代码经过压缩之后,ObjProto
就可能命名成a变量,那么原来的代码就压缩成
a.xxx = ...
一个小建议就是凡事一段代码被使用两次以上都建议定义变量(函数),有利于修改和压缩代码。
格式
29 var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind,
nativeCreate = Object.create;
这种定义的方式省略了多余的var,格式也美观,让我想到了sublime中的一个插件alignment。
数据判断
1194 _.isElement = function(obj) {return !!(obj && obj.nodeType === 1);};
判断是否为dom,dom的nodeType属性值为1。这里用!!
强转为boolean值
1200 _.isArray = nativeIsArray || function(obj) {return toString.call(obj) === '[object Array]';};
判断是否为数组。由于Array.isArray函数是ECMAScript 5新增函数,所以为了兼容之前的版本,在原生判断函数不存在的情况下,后面重写了一个判断函数。用call函数来改变作用域可以避免当obj没有toString函数报错的情况。
1205 _.isObject = function(obj) {var type = typeof obj;return type === 'function' || type === 'object' && !!obj;
};
判断是否为对象。先用typeof判断数据类型。函数也属于对象,但是由于typeof null也是object,所以用!!obj来区分这种情况。
1219 if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {return _.has(obj, 'callee');
};
}
判断是否为arguments,很简单,arguments有个特有属性callee。
1239 _.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj;};
NaN这个值有两个特点:1.它是一个数;2.不等于它自己。
‘+’放在变量前面一般作用是把后面的变量变成一个数,在这里已经判断为一个数仍加上’+’,是为了把var num = new Number()
这种没有值的数字也归为NaN。
1244 _.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};
是不是以为如果是布尔值不是true就是false?还有第3中情况var b = new Boolean()
。b也是布尔值。
1254 _.isUndefined = function(obj) {
return obj === void 0;
};
用void 0来表示undefined,非常有意思的小技巧。不过常用方式还是if(xxx)来判断是不是undefined。
eq
是underscore的一个内置函数,代码太长,不粘贴了。isEmpty调用了这个函数。整个思路由易到难,先用===比较简单数据,然后用toString来判断是否相等,最后用递归处理复杂的Array、Function和Object对象。
1091 if (a === b) return a !== 0 || 1 / a === 1 / b;
这里为了区分’+0’和’-0’,因为这两个数对计算结果是有影响的。
1098 var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {// Strings, numbers, regular expressions, dates, and booleans are compared by value.case '[object RegExp]':// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')case '[object String]':// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is// equivalent to `new String("5")`.return '' + a === '' + b;case '[object Number]':// `NaN`s are equivalent, but non-reflexive.// Object(NaN) is equivalent to NaNif (+a !== +a) return +b !== +b;// An `egal` comparison is performed for other numeric values.return +a === 0 ? 1 / +a === 1 / b : +a === +b;case '[object Date]':case '[object Boolean]':// Coerce dates and booleans to numeric primitive values. Dates are compared by their// millisecond representations. Note that invalid dates with millisecond representations// of `NaN` are not equivalent.return +a === +b;
}
这里是对简单对象进行判断,分为两类,一类是String
和RegExp
,这种数据直接toString
然后判断。另一类是Number
、Date
和Boolean
,通过转换成数字判断。
1150 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();
对于数组和对象只能用递归了,同时用aStack和bStack来暂存递归中的子对象。这里一个小技巧的就是先判断数组/属性的长度,如果不相等可以有效地减少递归。
先看一下两个比较重要的内部函数
63 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);};case 2: return function(value, other) {return func.call(context, value, other);};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);};};
这个函数是underscore内部很重要的函数,主要用来执行函数并改变所执行函数的作用域,最后加了一个argCount参数来指定参数个数,对参数个数小于等于4的情况进行分类处理。对不同参数的解释大概是:
1的情况一般是用在接受单值的情况,比如times,sortedIndex之类的函数。
2的情况据说是给比如jQuery,zepto事件绑定,代理什么的,但是在源代码中没有看到被调用。
3的情况用于迭代器函数,比如foreach,map,pick等。
4的情况用reduce和reduceRight函数。
87 var cb = function(value, context, argCount) {if (value == null) return _.identity;if (_.isFunction(value)) return optimizeCb(value, context, argCount);if (_.isObject(value)) return _.matcher(value);return _.property(value);};
这也是一个比较常用的内部函数,只是对参数进行了判断:如果是函数则返回上面说到的回调函数;如果是对象则返回一个能判断对象是否相等的函数;默认返回一个获取对象属性的函数。
140 _.each = _.forEach = function(obj, iteratee, context) {iteratee = optimizeCb(iteratee, context);var i, length;if (isArrayLike(obj)) {for (i = 0, length = obj.length; i < length; i++) {iteratee(obj[i], i, obj);}} else {var keys = _.keys(obj);for (i = 0, length = keys.length; i < length; i++) {iteratee(obj[keys[i]], keys[i], obj);}}return obj;};// Return the results of applying the iteratee to each element._.map = _.collect = function(obj, iteratee, context) {iteratee = cb(iteratee, context);var keys = !isArrayLike(obj) && _.keys(obj),length = (keys || obj).length,results = Array(length);for (var index = 0; index < length; index++) {var currentKey = keys ? keys[index] : index;results[index] = iteratee(obj[currentKey], currentKey, obj);}return results;};
从代码上看,each函数是包括map函数的,map只能处理对象,each可以处理对象和数组。至于forEach和collect在API文档中看不到,应该是为了兼容以前老版本做的别名处理。
170 function createReduce(dir) {// Optimized iterator function as using arguments.length// in the main function will deoptimize the, see #1991.function iterator(obj, iteratee, memo, keys, index, length) {for (; index >= 0 && index < length; index += dir) {var currentKey = keys ? keys[index] : index;memo = iteratee(memo, obj[currentKey], currentKey, obj);}return memo;}return function(obj, iteratee, memo, context) {iteratee = optimizeCb(iteratee, context, 4);var keys = !isArrayLike(obj) && _.keys(obj),length = (keys || obj).length,index = dir > 0 ? 0 : length - 1;// Determine the initial value if none is provided.if (arguments.length < 3) {memo = obj[keys ? keys[index] : index];index += dir;}return iterator(obj, iteratee, memo, keys, index, length);};}
这个是reduce和reduceRight调用的内部函数,将memo这个变量作为入参传递给iterator函数,调用自定义的iteratee函数进行循环处理,每次处理完的结果都赋值给memo变量,最后返回memo变量的结果。这里有两个问题
- 为什么这里不按照常理逻辑来写代码而要用闭包呢?闭包大致有这么几个作用:避免命名冲突;私有化变量;变量持久化。这里的作用主要就是变量(函数)持久化,好处就是重复调用的时候不需要再重新创建函数,从而提升执行速度。
为什么要用两层闭包呢?第一层闭包持久化iterator函数,调用reduce和reduceRight函数避免重复新建函数。第二层闭包保存keys,index,length这些变量。
263 _.invoke = function(obj, method) {
var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) {var func = isFunc ? method : value[method];return func == null ? func : func.apply(value, args); });
};
这里用slice.call(arguments, 2)
来获取后面的不定参数,然后用func.apply(value, args)
来传入该参数比较有意思。
转载:js高手进阶之路:underscore源码经典
underscore源码经典--收藏相关推荐
- 一次发现underscore源码bug的经历以及对学术界『拿来主义』的思考
事情是如何发生的 最近干了件事情,发现了 underscore 源码的一个 bug.这件事本身并没有什么可说的,但是过程值得我们深思,记录如下,各位看官仁者见仁智者见智. 平时有浏览园区首页文章的习惯 ...
- underscore源码剖析之整体架构
前言 最近打算好好看看underscore源码,一个是因为自己确实荒废了基础,另一个是underscore源码比较简单,比较易读. 本系列打算对underscore1.8.3中关键函数源码进行分析,希 ...
- Underscore源码阅读极简版入门
看了网上的一些资料,发现大家都写得太复杂,让新手难以入门.于是写了这个极简版的Underscore源码阅读. 源码: github.com/hanzichi/un- 一.架构的实现 1.1:架构 (f ...
- underscore源码解析
前言 underscore是最适合初级人士阅读的源码,在阅读源码时,有一些有趣的实现,记录如下. 基于underscore1.8.3. 留存root // Establish the root obj ...
- webbrowser抓取php网页源码,获取webbrowser控件 网页的源码(收藏)
获取webbrowser控件 网页的源码(收藏) 翻译|其它|编辑:郝浩|2005-04-28 09:45:00.000|阅读 3152 次 概述: 我在网上找到使用rft控件保存webbrowse文 ...
- 学习underscore源码整体架构,打造属于自己的函数式编程类库
前言 上一篇文章写了 jQuery整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库 虽然看过挺多 underscore.js分析类的文章,但总感觉少点什么.这也许就是纸上得来终觉 ...
- android中国象棋游戏源码,经典的android 象棋源码,象棋规则完整。
[实例简介] 经典的android 象棋源码,实现简单的人机对战,机器端算法简单,但象棋规则值得学习. [实例截图] [核心代码] Chess └── Chess ├── AndroidManifes ...
- C/C++ linux 分享库源码网站收藏
文章目录 launchpad videolan launchpad https://launchpad.net/ 简介: Launchpad is a software collaboration p ...
- 打卡日历html源码,经典模式打卡日历_闯关模式打课时列表.html
经典模式打卡日历/闯关模式打课时列表 $axure.utils.getTransparentGifPath = function() { return 'resources/images/transp ...
最新文章
- [译]Java 设计模式之命令
- Multi-Temporal SAR Data Large-Scale Crop Mapping Based on U-Net Model(利用U-net对多时相SAR影像获得作物图)...
- Maven学习-使用Nexus搭建Maven私服
- Java笔记-基于Spring Boot的SOAP双向SSL认证及WS-Security
- 剑指offer——判断树的子结构
- HDU 4630 No Pain No Game (线段树+离线)
- .NET网络编程学习(三)--网络蜘蛛程序(Spider)
- Unity开发备忘录000020:Unity2019如何切换成中文界面
- 360安全卫士怎么打开加速球
- 极域电子教室常见问题的解决方法
- 飞秋2012、飞秋2013资源文件
- EDM模板编写踩坑指南(持续更新中)
- Python黑客系列之——控制自己的手机摄像头拍照,并自动发送到邮箱。
- 计算机内无法使用搜狗,技巧:IE11无法使用搜狗输入法的原因及解决方法
- 敬畏传奇——直面第一台可编程电子计算机:Colossus
- 【耀杨的前世今生】耀杨的毕生所学——《狗叫江湖》之“葫芦给学习法”(1)
- Android 字体 hsv 对比度,Android图像锐化,饱和度,色调,亮度和对比度
- 13,excel vba 代码的简化和重复使用_初识对像
- 小学-知识与能力【3】
- 2021年低压电工考试资料及低压电工模拟试题