var each = function (ary, callback) { for (var i = 0, l = ary.length; i < l; i++) { callback.call(ary[i], i, ary[i]); // 把下标和元素当作参数传给 callback 函数 } };
each([1, 2, 3], function (i, n) { alert([i, n]); });
7.3 内部迭代器和外部迭代器
1. 内部迭代器
上节的 each 函数属于内部迭代器,each 函数的内部已经定义好了迭代规则,完全接手整个迭代过程,外部只需要一次初始调用。内部迭代器调用非常方便,外界不用关心迭代器内部实现,跟迭代器的交互也仅是一次初始调用,但这也刚好是内部迭代器的缺点。由于内部迭代器的迭代规则已经被提前规定,上面的 each 函数就无法同时迭代 2 个数组了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 比较两个数组是否相等 var compare = function (ary1, ary2) { if (ary1.length !== ary2.length) { thrownewError('ary1 和 ary2 不相等'); } each(ary1, function (i, n) { if (n !== ary2[i]) { thrownewError('ary1 和 ary2 不相等'); } }); alert('ary1 和 ary2 相等'); };
$.each = function (obj, callback) { var value, i = 0, length = obj.length, isArray = isArraylike(obj);
if (isArray) { // 迭代类数组 for (; i < length; i++) { value = callback.call(obj[i], i, obj[i]);
if (value === false) { break; } } } else { for (i in obj) { // 迭代 object 对象 value = callback.call(obj[i], i, obj[i]); if (value === false) { break; } } }
return obj; };
7.5 倒序迭代器
GoF 中对迭代器模式的定义非常松散,因此可以有多种迭代器的实现。
如倒序迭代器:
1 2 3 4 5 6 7 8 9
var reverseEach = function (ary, callback) { for (var l = ary.length - 1; l >= 0; l--) { callback(l, ary[l]); } };
var each = function (ary, callback) { for (var i = 0, l = ary.length; i < l; i++) { if (callback(i, ary[i]) === false) { // callback 的执行结果返回 false,提前终止迭代 break; } } };
each([1, 2, 3, 4], function (i, n) { if (n > 3) { // n 大于 3 的时候终止循环 returnfalse; } console.log(n); // 分别输出:1,2,3 });
7.7 迭代器模式的应用举例
假设需求:根据不同的浏览器获取相应的上传组件对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 旧代码 var getUploadObj = function () { try { returnnewActiveXObject('TXFTNActiveX.FTNUpload'); // IE 上传控件 } catch (e) { if (supportFlash()) { // supportFlash 函数未提供 var str = '<object type="application/x-shockwave-flash"></object>'; return $(str).appendTo($('body')); } else { var str = '<input name="file" type="file" />'; // 表单上传 return $(str).appendTo($('body')); } } };
以上代码为了得到一个 upload 对象,函数里充斥着 try,catch 以及 if 条件分支。缺点第一难以阅读,第二严重违反封闭-开放原则。在开发和调试过程中,需要来回切换不同的上传方式,每次改动都很痛苦。如果要增加支持另外的上传方式,唯一的方法是继续往函数里增加条件分支。
var getActiveUploadObj = function () { try { returnnewActiveXObject('TXFTNActiveX.FTNUpload'); } catch (e) { returnfalse; } }
var getFlashUploadObj = function () { if (supportFlash()) { var str = '<object type="application/x-shockwave-flash"></object>'; return $(str).appendTo($('body')); } returnfalse; }
var getFormUploadObj = function () { var str = '<input name="file" type="file" />'; return $(str).appendTo($('body')); };
var iteratorUploadObj = function () { for (var i = 0, fn; fn = arguments[i++];) { var uploadObj = fn(); if (uploadObj !== false) { return uploadObj; } } };
var uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj);
重构代码后,获取不同上传对象的方法被隔离在各自的函数里互不干扰,try、catch 和 if 分支不再纠缠在一起,使得我们可以很方便地维护和扩展代码。比如后来再增加 Webkit 控件上传和 HTML5 上传,只需:
1 2 3 4 5 6 7 8 9
var getWebkitUploadObj = function () { // 具体代码略 }
var getHtml5UploadObj = function () { // 具体代码略 }
var uploadObj = iteratorUploadObj(getActiveUploadObj, getWebkitUploadObj, getFlashUploadObj, getHtml5UploadObj, getFormUploadObj);