Prototype源码浅析 Enumerable部分(二)


Posted in Javascript onJanuary 18, 2012

前面each方法中掉了一个方面没有说,就是源码中的$break和$continue。这两个变量是预定义的,其作用相当于普通循环里面的break和continue语句的作用。出于效率的考虑,在某些操作中并不需要完全遍历一个集合(不局限于一个数组),所以break和continue还是很必要的。
对于一个循环来说,对比下面几种退出循环的方式:

var array_1 = [1,2,3]; 
var array_2 = ['a','b','c']; 
(function(){ 
for(var i = 0, len = array_1.length; i < len; i++){ 
for(var j = 0, len_j = array_1.length; i < len_j; j++){ 
if('c' === array_2[j]){ 
break; 
} 
console.log(array_2[j]); 
} 
} 
})();//a,b,a,b,a,b 
(function(){ 
for(var i = 0, len = array_1.length; i < len; i++){ 
try{ 
for(var j = 0, len_j = array_1.length; i < len_j; j++){ 
if('c' === array_2[j]){ 
throw new Error(); 
} 
console.log(array_2[j]); 
} 
}catch(e){ 
console.log('退出一层循环'); 
} 
} 
})();//a,b,'退出一层循环',a,b,'退出一层循环',a,b,'退出一层循环' 
(function(){ 
try{ 
for(var i = 0, len = array_1.length; i < len; i++){ 
for(var j = 0, len_j = array_1.length; i < len_j; j++){ 
if('c' === array_2[j]){ 
throw new Error(); 
} 
console.log(array_2[j]); 
} 
} 
}catch(e){ 
console.log('退出一层循环'); 
} 
})();//a,b,'退出一层循环'

当我们把错误捕获放在相应的循环层面时,就可以中断相应的循环。可以实现break和break label的作用(goto)。这样的一个应用需求就是可以把中断挪到外部去,恰好符合Enumerable处的需求。

回到Enumerable上来,由于each(each = function(iterator, context){})方法的本质就是一个循环,对于其第一个参数iterator,并不包含循环,因此直接调用break语句会报语法错误,于是Prototype源码中采用上面的第二种方法。

Enumerable.each = function(iterator, context) { 
var index = 0; 
try{ 
this._each(function(value){ 
iterator.call(context, value, index++); 
}); 
}catch(e){ 
if(e != $break){ 
throw e; 
} 
} 
return this; 
};

一旦iterator执行中抛出一个$break,那么循环就中断。如果不是$break,那么就抛出相应错误,程序也稳定点。这里的$break的定义并没有特殊要求,可以按照自己的喜好随便更改,不过意义不大。

Enumerable中的某些方法在一些现代浏览器里面已经实现了(参见chrome原生方法之数组),下面是一张对比图:

Prototype源码浅析 Enumerable部分(二)
在实现这些方法时,可以借用原生方法,从而提高效率。不过源码中并没有借用原生的部分,大概是因为Enumerable除了混入Array部分外,还需要混入其他的对象中。

看上面的图示明显可以看得出,each和map 的重要性,map其实本质还是each,只不过each是依次处理集合的每一项,map是在each的基础上,还把处理后的结果返回来。在Enumerable内部,map是collect方法的一个别名,另一个别名是select,其内部全部使用的是collect这个名字。

检测:all | any | include

这三个方法不涉及对原集合的处理,返回值均是boolean类型。

all : 若 Enumerable 中的元素全部等价于 true,则返回 true,否则返回 false

function all(iterator, context) { 
var result = true; 
this.each(function(value, index) { 
result = result && !!iterator.call(context, value, index); 
}); 
return result; 
}

对于all方法来说,里面的两个参数都不是必须的,所以,内部提供了一个函数,以代替没有实参时的iterator,直接返回原值,名字叫做Prototype.K。Prototype.K的定义在库的开头,是一个返回参数值的函数Prototype.K = function(x){return x;}。另外,在all方法中,只要有一个项的处理结果为false,整个过程就可以放弃(break)了,于是用到了本文开头的中断循环的方法。最后的形式就是:

Prototype.K = function(){}; 
Enumerable.all = function(iterator, context) { 
iterator = iterator || Prototype.K; 
var result = true; 
this.each(function(value, index) { 
result = result && !!iterator.call(context, value, index); 
if (!result) throw $break; 
}); 
return result; 
}

最后返回的result是一个boolean型,偏离一下all,我们改一下result:

function collect(iterator, context) { 
iterator = iterator || Prototype.K; 
var results = []; 
this.each(function(value, index) { 
results.push(iterator.call(context, value, index)); 
}); 
return results; 
}

此时results是一个数组,我们不中断处理过程,保存所有的结果并返回,恩,这就是collect方法,或者叫做map方法。

any:若 Enumerable 中的元素有一个或多个等价于 true,则返回 true,否则返回 false,其原理和all差不多,all是发现false就收工,any是发现true就收工。

function any(iterator, context) { 
iterator = iterator || Prototype.K; 
var result = false; 
this.each(function(value, index) { 
if (result = !!iterator.call(context, value, index)) 
throw $break; 
}); 
return result; 
}

include:判断 Enumerable 中是否存在指定的对象,基于 == 操作符进行比较

这个方法有一步优化,就是调用了indexOf方法,对于数组来说,indexOf返回-1就不可以知道相应元素不存在了,如果集合没有indexOf方法,就只能查找比对了。这里的查找和没有任何算法,一个个遍历而已,如果要改写也容易,不过平时应用不多,因此估计也没有花这个精力去优化这个。所以如果结果为true的时候效率比结果为false的时候要高一些,看运气了。

function include(object) { 
if (Object.isFunction(this.indexOf))//这个判定函数应该很熟悉了 
if (this.indexOf(object) != -1) return true;//有indexOf就直接调用 var found = false; 
this.each(function(value) {//这里的效率问题 
if (value == object) { 
found = true; 
throw $break; 
} 
}); 
return found; 
}

下面是一组过滤数据的方法:返回单个元素:max | min | detect返回一个数组:grep | findAll | reject | partition 其中max和min并不局限于数字的比较,字符的比较一样可以。max(iterator, context)依旧可以带有两个参数,可以先用iterator处理之后再来比较值,这样的好处就是不必局限于特定的数据类型,比如,对象数组按照一定规则取最大值:

console.dir([{value : 3},{value : 1},{value : 2}].max(function(item){ 
return item.value; 
}));//3

因此源码的实现方式可以想象,直接比较的时候,实现方式可以如下:
function max() { 
var result; 
this.each(function(value) { 
if (result == null || value >= result) //result==null是第一次比较 
result = value; 
}); 
return result; 
}

扩展之后,value要进一步变为value = (iterator处理后的返回值):
function max(iterator, context) { 
iterator = iterator || Prototype.K; 
var result; 
this.each(function(value, index) { 
value = iterator.call(context, value, index); 
if (result == null || value >= result) 
result = value; 
}); 
return result; 
}

min的原理也一样。detect和any的原理和接近,any是找到一个true就返回true,detect是找到一个true就返回满足true条件的那个值。源码就不贴了。grep 这个很眼熟啊,一个unix/linux工具,其作用也很眼熟——就是返回所有和指定的正则表达式匹配的元素。只不过unix/linux只能处理字符串,这里扩展了范围,但是基本形式还是没有变。如果集合的每一项都是字符串,那么实现起来回事这样:
Enumerable.grep = function(filter) { 
if(typeof filter == 'string'){ 
filter = new RegExp(filter); 
} 
var results = []; 
this.each(function(value,index){ 
if(value.match(filter)){ 
results.push(value); 
} 
}) 
return results; 
};

但是有一现在要处理的集合可能并都是字符串,为了达到更广泛的应用,首先要考虑的就是调用形式。看上面的实现,注意这么一句:
if(value.match(filter))
其中value是个字符串,match是String的方法,现在要扩展所支持的类型,要么给每一个value都加上match方法,要么转换形式。显然第一种巨响太大,作者转换了思路:
if (filter.match(value))
这么一来,不论value为何值,只要filter有对应的match方法即可,上面对于RegExp对象,是没有match方法的,于是在源码中,作者扩展了RegExp对象:
RegExp.prototype.match = RegExp.prototype.test;
注意上面的match和String的match有本质区别。这么一来,如果value是对象,我们的filter只需要提供相应的检测对象的match方法即可。于是就有:
function grep(filter, iterator, context) { 
iterator = iterator || Prototype.K; 
var results = []; if (Object.isString(filter)) 
filter = new RegExp(RegExp.escape(filter)); 
this.each(function(value, index) { 
if (filter.match(value))//原生filter是没有match方法的。 
results.push(iterator.call(context, value, index)); 
}); 
return results; 
}

对于匹配的结果,可以处理之后再返回,这就是iterator参数的作用。不同于max方法,grep是进行主要操作时候再用iterator来处理结果,max是用iterator处理源数据之后再来进行主要操作。因为grep中的filter代替了max中iterator的作用。至于findAll,是grep的加强版,看过grep,findAll就很简单了。reject就是findAll的双子版本,作用正好相反。partition就是findAll + reject,组合亲子版本。转载请注明来自小西山子【http://www.cnblogs.com/xesam/】

Javascript 相关文章推荐
Javascript 构造函数 实例分析
Nov 26 Javascript
js 复制或插入Html的实现方法小结
May 19 Javascript
浅析showModalDialog数据缓存问题(用禁止浏览器缓存解决)
Jul 09 Javascript
JS 弹出层 定位至屏幕居中示例
May 21 Javascript
JS实现漂亮的淡蓝色滑动门效果代码
Sep 23 Javascript
详解JavaScript中的Unescape()和String() 函数
Nov 09 Javascript
javascript动态生成树形菜单的方法
Nov 14 Javascript
JavaScript黑洞数字之运算路线查找算法(递归算法)实例
Jan 28 Javascript
Bootstrap的modal拖动效果
Dec 25 Javascript
JavaScript中undefined和null的区别
May 03 Javascript
Node.JS循环删除非空文件夹及子目录下的所有文件
Mar 12 Javascript
解决JS表单验证只有第一个IF起作用的问题
Dec 04 Javascript
JS中的public和private对象,即static修饰符
Jan 18 #Javascript
DOM 中的事件处理介绍
Jan 18 #Javascript
深入理解JavaScript系列(13) This? Yes,this!
Jan 18 #Javascript
JavaScript中常用的运算符小结
Jan 18 #Javascript
jQuery LigerUI 使用教程表格篇(1)
Jan 18 #Javascript
jQuery LigerUI 使用教程入门篇
Jan 18 #Javascript
c#和Javascript操作同一json对象的实现代码
Jan 17 #Javascript
You might like
php zlib压缩和解压缩swf文件的代码
2008/12/30 PHP
使用Sphinx对索引进行搜索
2013/06/25 PHP
本地计算机无法启动Apache故障处理
2014/08/08 PHP
SCP远程VPS快速搬家和WDCP升级php5.3安装memcached和eaccelerator教程
2017/07/27 PHP
MacOS下PHP7.1升级到PHP7.4.15的方法
2021/02/22 PHP
理解Javascript_06_理解对象的创建过程
2010/10/15 Javascript
jquery绑定原理 简单解析与实现代码分享
2011/09/06 Javascript
基于jQuery的倒计时实现代码
2012/05/30 Javascript
解析javascript 实用函数的使用详解
2013/05/10 Javascript
jQuery中delegate和on的用法与区别详细解析
2014/01/26 Javascript
JavaScript怎么判断图片是否加载完成以便获取其尺寸
2014/05/08 Javascript
JS实现简单的顶部定时关闭层效果
2014/06/15 Javascript
JavaScript定义变量和变量优先级问题探讨
2014/10/11 Javascript
JavaScript前端图片加载管理器imagepool使用详解
2014/12/29 Javascript
js给网页加上背景音乐及选择音效的方法
2015/03/03 Javascript
JavaScript转换与解析JSON方法实例详解
2015/11/24 Javascript
jQuery Raty 一款不错的星级评分插件
2016/08/24 Javascript
解析AngularJS中get请求URL出现的跨域问题
2016/12/01 Javascript
node.js学习之交互式解释器REPL详解
2016/12/08 Javascript
React+react-dropzone+node.js实现图片上传的示例代码
2017/08/23 Javascript
nodejs结合socket.io实现websocket通信功能的方法
2018/01/12 NodeJs
解决vue build打包之后首页白屏的问题
2018/03/06 Javascript
改变vue请求过来的数据中的某一项值的方法(详解)
2018/03/08 Javascript
JavaScript设计模式之缓存代理模式原理与简单用法示例
2018/08/07 Javascript
在Python中处理字符串之isdigit()方法的使用
2015/05/18 Python
Python之父谈Python的未来形式
2016/07/01 Python
python实现五子棋游戏(pygame版)
2020/01/19 Python
基于python的docx模块处理word和WPS的docx格式文件方式
2020/02/13 Python
scrapy结合selenium解析动态页面的实现
2020/09/28 Python
HTML5学习笔记之History API
2015/02/26 HTML / CSS
美体小铺加拿大官方网站:The Body Shop加拿大
2016/10/30 全球购物
亚马逊中国官方网站:amazon.cn
2017/05/25 全球购物
学生生病请假条范文
2014/02/16 职场文书
2015年七夕爱情寄语
2015/03/24 职场文书
浅谈node.js中间件有哪些类型
2021/04/29 Javascript
MySQL基础快速入门知识总结(附思维导图)
2021/09/25 MySQL