数组遍历、for...of、Interator接口与迭代器模式
youngwind opened this issue · 0 comments
顾名思义
近日,我又在思考这个问题:**在编程的世界中,如何高效地学习理论知识,应用理论知识来解决实际生产中的问题。**前人的研究已经硕果累累,列举几点如下:
- 理论知识往往是抽象的,要多用形象化的思维辅佐思考。
- 不要光看书,要多敲几遍demo。
- 当碰到实际难题的时候,回过头来看看相应的理论,刻意补足。这样下次就不会再掉到这个坑里了。
- 多联想,多与已学到的知识建立有效关联。因为有效关联越多,碰到实际难题的时候,想起对应理论知识的概率才会越大。
所以,今天我们的目标是:将数组遍历、for...of、Interator接口与迭代器模式这几个概念串联起来,以加强记忆和理解。
数组遍历
在ES5中,我们有3种方法可以遍历数组。而ES6又给我们多提供了一种for...of
。那么,他们之间各有什么优缺点呢?
以数组为例,JavaScript提供多种遍历语法。最原始的写法就是for循环。
for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); }这种写法比较麻烦,因此数组提供内置的forEach方法。
myArray.forEach(function (value) { console.log(value); });这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。
for...in循环可以遍历数组的键名。for (var index in myArray) { console.log(myArray[index]); }for...in循环有几个缺点。
- 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
- for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
- 某些情况下,for...in循环会以任意顺序遍历键名。
总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of循环相比上面几种做法,有一些显著的优点。for (let value of myArray) { console.log(value); }
- 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
- 不同于forEach方法,它可以与break、continue和return配合使用。
- 提供了遍历所有数据结构的统一操作接口。
来源:阮一峰老师的es6教程
注:for...of
不同于for
和for...in
的地方还有一处:for...of
直接遍历的是数组的值,而非数组的下标,而for
和for...in
仅仅是遍历数组的下标(当然你可以通过下标进一步获取值。遍历下标还是遍历值,仅仅在于语言层面的区别才有意义)。所以ES6的for...of
可以说是实现了直接遍历数组的值的命令语句。
Interator接口
for...of
的内部实现原理又是什么呢? → 是 iterator接口
比如下面这样的代码:
var myArray = ['a','b','c'];
var it = myArray[Symbol.iterator]();
console.log(it.next()); // {value: 'a', done: false}
console.log(it.next()); // {value: 'b', done: false}
console.log(it.next()); // {value: 'c', done: false}
console.log(it.next()); // {value: undefined, done: true}
**在ES6中,数组内置了iterator接口,但是普通的对象不是。也就是说,for...of
无法直接用于普通对象的遍历。**如下图所示。
那有什么解决方案吗?
从图中我们可以看出,其实for...of
语句本质上是调用了iterator接口。所以对于任意的数据结构而言,只要部署了interator接口,就可以使用for...of
创建迭代器,然后通过迭代器遍历其中的值。
所以,下面我们来看看,如何给一个普通的对象部署iterator接口。
var myObject = {
a: 2,
b: 3
}
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function () {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next: function () {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
}
}
}
}
});
for(let v of myObject) {
console.log(v);
}
// 输出:
// 2
// 3
以上参考自《你不知道的JavaScript》上卷中的第二部分第3章第4小节。
Interator接口本质上是一个生成迭代器的函数,执行它,就可以生成一个迭代器,不断执行迭代器的next方法,就可以遍历该数据结构。
迭代器模式
迭代器生成函数(Interator)让我想起了一种设计模式:迭代器模式,曾探所著的《JavaScript设计模式与开发实践》中第7章讲的就是这种模式。
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
书中演示了一个文件上传的例子,情景是:在不同的浏览器环境下,选择的上传方式是不一样的。所以我们会优先使用控件上传。如果浏览器没有安装上传控件,则使用Flash上传,如果连Flash也没安装,那就只好使用浏览器原生的表单上传了。
看,这种场景其实就是遍历。特殊点在于,当遍历到合适的情况的时候,就停止遍历了。这不正是for...of所擅长的吗?
我们先来看看不使用for...of
的bad code
var getUploadObj = function(){
try {
return new ActiveXObject('TXFTNActiveX.FTNUpload'); // IE上传控件
} catch (e) {
try {
new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); // Flash上传控件
let str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($('body'));
} catch (e) {
let str = '<input name="file" type="file" calss="ui-file"/>'; // 表单上传
return $(str).appendTo($('body'));
}
}
}
var uploadObj = getUploadObj();
console.log(uploadObj); // [input, prevObject: Z.fn.init[1], context: undefined]
**如果以后要有更多上传的方式,那么代码中将嵌套更多的try...catch
和if...else
,可维护性非常差。**作者在书中重构了一版代码,写得非常精彩,具体的请直接看书。下面我给出我重构的版本,是基于for...of
的。
let getUploadObj = {
getActiveUploadObj (){
try {
return new ActiveXObject('TXFTNActiveX.FTNUpload'); // IE上传控件
} catch (e) {
return false;
}
},
getFalshUploadObj () {
try {
new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); // Flash上传控件
let str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($('body'));
} catch (e) {
return false;
}
},
getFormUploadObj () {
let str = '<input name="file" type="file" calss="ui-file"/>'; // 表单上传
return $(str).appendTo($('body'));
}
}
// 给对象getUploadObj定义iterator接口,上面演示过这段代码
// 这里可以通过工厂模式,抽象成一个专门给对象安装iterator接口的函数,这样就可以省却很多重复代码了。
Object.defineProperty(getUploadObj, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function(){
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next: function(){
return {
value: o[ks[idx++]],
done: (idx > ks.length)
}
}
}
}
});
function iteratorUploadObj (uploadObj){
// 直接使用`for...of`遍历uploadObj对象
for(let getUpload of uploadObj){
let uploadObj = getUpload();
if(uploadObj) return uploadObj;
}
}
let uploadObj = iteratorUploadObj(getUploadObj);
console.log(uploadObj); // [input, prevObject: Z.fn.init[1], context: undefined]
===完===