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;
}

这里是对简单对象进行判断,分为两类,一类是StringRegExp,这种数据直接toString然后判断。另一类是NumberDateBoolean,通过转换成数字判断。

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源码经典--收藏相关推荐

  1. 一次发现underscore源码bug的经历以及对学术界『拿来主义』的思考

    事情是如何发生的 最近干了件事情,发现了 underscore 源码的一个 bug.这件事本身并没有什么可说的,但是过程值得我们深思,记录如下,各位看官仁者见仁智者见智. 平时有浏览园区首页文章的习惯 ...

  2. underscore源码剖析之整体架构

    前言 最近打算好好看看underscore源码,一个是因为自己确实荒废了基础,另一个是underscore源码比较简单,比较易读. 本系列打算对underscore1.8.3中关键函数源码进行分析,希 ...

  3. Underscore源码阅读极简版入门

    看了网上的一些资料,发现大家都写得太复杂,让新手难以入门.于是写了这个极简版的Underscore源码阅读. 源码: github.com/hanzichi/un- 一.架构的实现 1.1:架构 (f ...

  4. underscore源码解析

    前言 underscore是最适合初级人士阅读的源码,在阅读源码时,有一些有趣的实现,记录如下. 基于underscore1.8.3. 留存root // Establish the root obj ...

  5. webbrowser抓取php网页源码,获取webbrowser控件 网页的源码(收藏)

    获取webbrowser控件 网页的源码(收藏) 翻译|其它|编辑:郝浩|2005-04-28 09:45:00.000|阅读 3152 次 概述: 我在网上找到使用rft控件保存webbrowse文 ...

  6. 学习underscore源码整体架构,打造属于自己的函数式编程类库

    前言 上一篇文章写了 jQuery整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库 虽然看过挺多 underscore.js分析类的文章,但总感觉少点什么.这也许就是纸上得来终觉 ...

  7. android中国象棋游戏源码,经典的android 象棋源码,象棋规则完整。

    [实例简介] 经典的android 象棋源码,实现简单的人机对战,机器端算法简单,但象棋规则值得学习. [实例截图] [核心代码] Chess └── Chess ├── AndroidManifes ...

  8. C/C++ linux 分享库源码网站收藏

    文章目录 launchpad videolan launchpad https://launchpad.net/ 简介: Launchpad is a software collaboration p ...

  9. 打卡日历html源码,经典模式打卡日历_闯关模式打课时列表.html

    经典模式打卡日历/闯关模式打课时列表 $axure.utils.getTransparentGifPath = function() { return 'resources/images/transp ...

最新文章

  1. [译]Java 设计模式之命令
  2. Multi-Temporal SAR Data Large-Scale Crop Mapping Based on U-Net Model(利用U-net对多时相SAR影像获得作物图)...
  3. Maven学习-使用Nexus搭建Maven私服
  4. Java笔记-基于Spring Boot的SOAP双向SSL认证及WS-Security
  5. 剑指offer——判断树的子结构
  6. HDU 4630 No Pain No Game (线段树+离线)
  7. .NET网络编程学习(三)--网络蜘蛛程序(Spider)
  8. Unity开发备忘录000020:Unity2019如何切换成中文界面
  9. 360安全卫士怎么打开加速球
  10. 极域电子教室常见问题的解决方法
  11. 飞秋2012、飞秋2013资源文件
  12. EDM模板编写踩坑指南(持续更新中)
  13. Python黑客系列之——控制自己的手机摄像头拍照,并自动发送到邮箱。
  14. 计算机内无法使用搜狗,技巧:IE11无法使用搜狗输入法的原因及解决方法
  15. 敬畏传奇——直面第一台可编程电子计算机:Colossus
  16. 【耀杨的前世今生】耀杨的毕生所学——《狗叫江湖》之“葫芦给学习法”(1)
  17. Android 字体 hsv 对比度,Android图像锐化,饱和度,色调,亮度和对比度
  18. 13,excel vba 代码的简化和重复使用_初识对像
  19. 小学-知识与能力【3】
  20. 2021年低压电工考试资料及低压电工模拟试题

热门文章

  1. 调用函数的ALV、面向对象的ALV设置带选择列
  2. windows server 2008 大量拷贝后释放内存
  3. 新媒体营销操作手法及案例分享-初贵民
  4. 5. 使用字符串库函数
  5. linux--私钥登陆
  6. 问题三十三:怎么用ray tracing画特殊长方体(box)
  7. 大数据分析如何应用在驾驶世界
  8. 五大维度深掘工业互联网数据价值
  9. 企业大数据的主要竞争优势
  10. python实现excel数据透视_在pywin32中创建Excel数据透视缓存