js 高手进阶之路:underscore源码经典(二)

网址:http://web.jobbole.com/83872/

underscore 源码版本 1.8.2

起因

很多人向我推荐研究js,可以看看一些第三方js类库的源码,而源码之中最好解读也最简短的就是underscore,它也是我平常比较喜欢的一个库,因为它性价比高:体积小、能力强。打开一看,才1000多行,试着读了一下,确实很值得一看,所以对精彩部分做了一下整理。

闭包

整个函数在一个闭包中,避免污染全局变量。通过传入this(其实就是window对象)来改变函数的作用域。和jquery的自执行函数其实是异曲同工之妙。这种传入全局变量的方式一方面有利于代码阅读,另一方面方便压缩。
underscore写法:

(function(){
    ...
}.call(this));

jquery写法:

(function(window, undefined) {
    ...
})(window);

原型赋值

 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

Array,Object,Function这些本质都是函数,获取函数原型属性prototype也是为了便于压缩。简单解释一下,如果代码中要扩展属性,可能这样写

Object.prototype.xxx = ...

而这种代码是不可压缩的,Object,prototype这些名字改了浏览器就不认得了。

但是上面的代码中创建了ObjProto之后,源生代码经过压缩之后,ObjProto就可能命名成a变量,那么原来的代码就压缩成

a.xxx = ...

一个小建议就是凡事一段代码被使用两次以上都建议定义变量(函数),有利于修改和压缩代码。

格式

var nativeIsArray      = Array.isArray,
nativeKeys         = Object.keys,
nativeBind         = FuncProto.bind,
nativeCreate       = Object.create;

这种定义的方式省略了多余的var,格式也美观,让我想到了sublime中的一个插件alignment。

数据判断

判断是否为dom,dom的nodeType属性值为1。这里用!!强转为boolean值

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

判断是否为数组。由于Array.isArray函数是ECMAScript 5新增函数,所以为了兼容之前的版本,在原生判断函数不存在的情况下,后面重写了一个判断函数。用call函数来改变作用域可以避免当obj没有toString函数报错的情况。

_.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === ‘[object Array]‘;
  };

判断是否为对象。先用typeof判断数据类型。函数也属于对象,但是由于typeof null也是object,所以用!!obj来区分这种情况。

_.isObject = function(obj) {
    var type = typeof obj;
    return type === ‘function‘ || type === ‘object‘ && !!obj;
};

判断是否为arguments,很简单,arguments有个特有属性callee。

 if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
  return _.has(obj, ‘callee‘);
};
}

NaN这个值有两个特点:1.它是一个数;2.不等于它自己。
‘+’放在变量前面一般作用是把后面的变量变成一个数,在这里已经判断为一个数仍加上’+’,是为了把var num = new Number()这种没有值的数字也归为NaN。

_.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj;
  };

是不是以为如果是布尔值不是true就是false?还有第3中情况var b = new Boolean()。b也是布尔值。

 _.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) === ‘[object Boolean]‘;
};

用void 0来表示undefined,非常有意思的小技巧。不过常用方式还是if(xxx)来判断是不是undefined。

 _.isUndefined = function(obj) {
return obj === void 0;
};

eq是underscore的一个内置函数,代码太长,不粘贴了。isEmpty调用了这个函数。整个思路由易到难,先用===比较简单数据,然后用toString来判断是否相等,最后用递归处理复杂的Array、Function和Object对象。

if (a === b) return a !== 0 || 1 / a === 1 / b;

这里为了区分’+0’和’-0’,因为这两个数对计算结果是有影响的。

 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 NaN
    if (+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,通过转换成数字判断。

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来暂存递归中的子对象。这里一个小技巧的就是先判断数组/属性的长度,如果不相等可以有效地减少递归。

时间: 2024-08-25 12:20:21

js 高手进阶之路:underscore源码经典(二)的相关文章

underscore源码经典

转帖: http://web.jobbole.com/83872/ underscore 源码版本 1.8.2 起因 很多人向我推荐研究js,可以看看一些第三方js类库的源码,而源码之中最好解读也最简短的就是underscore,它也是我平常比较喜欢的一个库,因为它性价比高:体积小.能力强.打开一看,才1000多行,试着读了一下,确实很值得一看,所以对精彩部分做了一下整理. 闭包 整个函数在一个闭包中,避免污染全局变量.通过传入this(其实就是window对象)来改变函数的作用域.和jquer

java基础进阶篇(七)_LinkedHashMap------【java源码栈】

目录 一.概述 二.特点 三.应用场合 四.构造方法 1.参数为空 2.accessOrder 五.源码结构分析 六.常见问题 1.如何实现的元素有序? 2.如何保证顺序的正确以及同步 3.如何实现两种顺序(插入顺序或者访问顺序)? 4.为什么重写containsValue()而不重写containsKey()? 七.常用方法 一.概述 ??LinkedHashMap是HashMap的子类,关于HashMap可以看下前面的章节:java基础进阶篇 HashMap public class Lin

underscore源码学习(1)

if(obj.length === +obj.length) {// 对<数组>中每一个元素执行处理器方法 for(var i = 0, l = obj.length; i < l; i++) { if( i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; } } obj.length === +obj.length        //判断是否为数字 if (typeof obj.

underscore源码阅读记录

这几天有大神推荐读underscore源码,趁着项目测试的空白时间,看了一下. 整个underscore包括了常用的工具函数,下面以1.3.3源码为例分析一下. _.size = function(obj) { return _.isArray(obj) ? obj.length : _.keys(obj).length; }; 整个underscore源码基本上都是以上这种方式写的,所以弄懂上面这段源码,整个underscore的源码就大概清楚了六七成了,剩下的源码用些时间,也就迎刃而解. 因

可视化工具gephi源码探秘(二)---导入netbeans

在上篇<可视化工具gephi源码探秘(一)>中主要介绍了如何将gephi的源码导入myeclipse中遇到的一些问题,此篇接着上篇而来,主要讲解当下通过myeclipse导入gephi源码的可行性不高以及熟悉netbeans,并把原本基于netbeans平台开发的gephi源码导入进netbeans后启动正常运行的过程,其中有遇到的不少问题和相应的解决方法. 前日工作梗概(还是沿着想把源码导入myeclipse的思路): 经过从各大子模块的pom.xml中筛选出符合条件的jar包写入项目下的p

netty 源码分析二

以服务端启动,接收客户端连接整个过程为例分析, 简略分为 五个过程: 1.NioServerSocketChannel 管道生成, 2.NioServerSocketChannel 管道完成初始化, 3.NioServerSocketChannel注册至Selector选择器, 4.NioServerSocketChannel管道绑定到指定端口,启动服务 5.NioServerSocketChannel接受客户端的连接,进行相应IO操作 Ps:netty内部过程远比这复杂,简略记录下方便以后回忆

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

哇!板球 源码分析二

游戏主页面布局 创建屏下Score标签 pLabel = CCLabelTTF::create("Score", "Arial", TITLE_FONT_SIZE); //分数标签 //设置标签字体的颜色 pLabel->setColor (ccc3(0, 0, 0)); //设置文本标签的位置 pLabel->setPosition ( ccp ( SCORE_X, //X坐标 SCORE_Y //Y坐标 ) ); //将文本标签添加到布景中 this

Spring 源码解析之HandlerAdapter源码解析(二)

Spring 源码解析之HandlerAdapter源码解析(二) 前言 看这篇之前需要有Spring 源码解析之HandlerMapping源码解析(一)这篇的基础,这篇主要是把请求流程中的调用controller流程单独拿出来了 解决上篇文章遗留的问题 getHandler(processedRequest) 这个方法是如何查找到对应处理的HandlerExecutionChain和HandlerMapping的,比如说静态资源的处理和请求的处理肯定是不同的HandlerMapping ge