1.相比for循环, forEach的优点在哪里?
- 语义化更好
- 需要管理维护的变量变少.
- 多层循环时更加明显.
- 复杂度降低
- 什么是迭代器?
- 迭代器是专门为可迭代对象设计的统一接口, 用来遍历数据
特点
- 每个迭代器都有next方法, 每次执行next方法,都会返回结果对象{value,done}
- value: 每次迭代的数据
- done: 迭代是否结束
- 每个迭代器都有专用指针,开始时指向数据结构的第一个值,每次调用next()指针向下移一位
- 每个迭代器支持for of 循环
写数组的迭代器
let arr = [1,2,3,4,5];
function createIterater (arr = []) {
let nextIndex = 0;
return {
next(){
if (arr.length - 1 < nextIndex ) {
return {
value : undefined,
done : true
}
}else{
return {
value : arr[nextIndex++],
done : false
}
}
}
}
}
let nod = createIterater(arr);
es6提供的 迭代器接口
- arr,map,set
- entries()
- keys()
- values()
let arr = [1,2,3];
let map = new Map([[1,2],['name','mike'],['age',18]]);
let set = new Set(arr);
console.log(map);
console.log(set);
let arrentry = arr.entries();/[index,value]
let mapentry = map.entries();/[key,value]
let setentry = set.entries();/[key,key]
支持 for of
for(let item of arrentry) {
console.log(item);
}
for(let item of mapentry) {
console.log(item);
}
for(let item of setentry) {
console.log(item);
}

for(let [key,value] of mapentry) {
console.log(key,value);
}

for of 调用的就是 next方法
for(let [key,value] of mapentry) {
console.log(key,value);
}
mapentry.next()

keys() , values() 类似
let arrkeys = arr.keys();
let mapkeys = map.keys();
let setkeys = set.keys();
for(let key of mapkeys) {
console.log(key);
}
mapkeys.next()

let arrvalues = arr.values();
let mapvalues = map.values();
let setvalues = set.values();
for(let value of mapvalues) {
console.log(value);
}
mapvalues.next()
image.png
每个可迭代的数据,都可以用for of
for(let value of arr) {
console.log(value);
}
实际上是通过arr的默认迭代器接口
所有的迭代器都有 Symbal.iterator , 必须以中括号方式访问[Symbal.iterator],返回默认迭代器
for(let value of arr[Symbol.iterator]()) {
console.log(value);
}
疑问
我们很容易发现一个事实, 调用next之后, 指针只能向后移动
假设我们对一个arr迭代器进行了for of 遍历, next走完指针
请问, 如果我们对该 arr 再来一次 for of 循环会如何?
能否遍历?
不能
for(let key of mapkeys) {
console.log(key);
}
for(let key of mapkeys) {
console.log(key,123);
}

疑问
既然无法用 for of 遍历同一个迭代器两次,
请问for of 能遍历同一个 arr 两次嘛?
可以
for(let item of arr){
console.log(item);
}
for(let item of arr){
console.log(item,123);
}

疑问, 可以认为每次for of 遍历 arr的时候,
实际上每次生成的 arr[Symbal.iterator]() 都是新的迭代器.
合理 因为形式上函数每次都执行, 返回新的迭代器对象.
迭代原理
我们可以知道指针的移动必然是线性遍历
数组中 迭代器的指针, 每次指向下一个指针
map set 中 会把邻接链表转换成单向链表
默认调用的规则
for(let item of arr){/arr[Symbol.iterator]() => 调用的规则是 arr.values();
console.log(item);
}
for(let item of map){/map[Symbol.iterator]() => 调用的规则是 map.entries();
console.log(item,123);
}
for(let item of set){/set[Symbol.iterator]() => 调用的规则是 set.values();
console.log(item,123);
}


对象是不可迭代的, 因为顺序不确定?
字符串的迭代
双字节词
let str = "a𠮷b";
console.log(str);
console.log(str.length);/4
console.log(str[0]);/ a
console.log(str[1]);/无法输出
console.log(str[2]);/无法输出
console.log(str[3]);/ b

可以用迭代器的 for of?
let str = "a𠮷b";
console.log(str);
for(let key of str) {
console.log(key);
}

不能用 for in
for(let key in str) {
console.log(str[key]);
}

疑问能不能转换成数组再遍历?
不能
let arr = str.split('');
console.log(arr);
for(let key in arr) {
console.log(arr[key]);
}

也就是说, split 遇到双字节字体的时候, 是失效的
生成器 generator
用于生成 迭代器
function *createIterator () {
console.log("first");
yield 1;
console.log("second");
yield 2;
console.log("third");
yield 3;
console.log("over");
}
i = createIterator();

可以看出 yield 是指针,每次 执行next, 会执行前一个指针到这一个指针之间的代码块
function *createIterator () {
let i;
yield i = 1;
console.log(i);
yield i = 2;
console.log(i);
yield 3;
}

可以看出 yield 可以返回一个变量
可以看出这些代码块之间是同一个作用域.
function *createIterator (arr = []) {
console.log('start');
for(let i = 0;i < arr.length;i++) {
console.log(arr[i]);
yield arr[i]
}
}
i = createIterator([1,2,3,,4,5]);

可以看出
- 可以用for循环
- for循环里面的代码被认为是两个yield之间的代码块, (也正常)
- yield 值 是 undefined 并不会导致迭代停止.
- yield 能暂停for循环
function *createIterator (arr = []) {
arr.forEach(function (item) {
yield item;
})
}
i = createIterator([1,2,3,,4,5]);

可以看出
- 不允许函数嵌套
- 不允许在普通函数中使用 yield
function *createIterator () {
yield 1;
yield 2;
yield 3;
return 8;
yield 4;
}
i = createIterator();

可以看出
- 遇到return 后面的yield 将失去指针作用
- 默认情况可以认为所有yield 指针之后 有个 return undefined 用来终止
- 可以认为 return 能够停止 next
可以测试一下
function *createIterator () {
let flag = true;
let i = 0;
while (flag){
i++;
yield i;
if (i > 3) {
return
}
}
}
i = createIterator();

高级部分, 可以把异步的描述成同步的!
问题是这样的, 我们常常有异步处理事情的时候,
但异步处理事情时, 我们还需要控制执行顺序
异步当中解决执行顺序的最基本的方案就是回调函数.
function first () {
console.log('first ing');
setTimeout(function () {
console.log("first");
second();
},1000)
}
function second () {
console.log('second ing');
setTimeout(function () {
console.log("second");
third();
},2000)
}
function third () {
console.log('third ing');
setTimeout(function () {
console.log("third");
},1000)
}
但这么写, 就要求我们每个函数都要清楚的知道,
自己下一个回调函数是谁,
依赖图不够直观,
我们想把 嵌套的变成线性的, 或者看起来像是线性的.
这也很合理, 因为执行顺序是依次的, 所以应该可以表现成线性的.
利用 生成器,
function first () {
console.log('first ing');
setTimeout(function () {
console.log("first");
process.next();
},1000)
}
function second () {
console.log('second ing');
setTimeout(function () {
console.log("second");
process.next();
},2000)
}
function third () {
console.log('third ing');
setTimeout(function () {
console.log("third");
},1000)
}
function *createIterator (arr = []) {
yield first();
yield second();
yield third();
}
let process = createIterator();
异步执行顺序在createIterator 中可以看得很清楚
而在各自的回调函数中则不必关心具体的下一个回调是谁.
yield的返回值? (我觉得叫做返回值稍微不恰当)
function *createIterator () {
let aaa;
aaa = yield 1;
console.log(aaa);
aaa = yield 2;
console.log(aaa);
aaa = yield 3;
console.log(aaa);
}
let process = createIterator();

可以理解为
- aaa = yield 1 可以看成是 设置了一个形参,用来接收下一次next()时传的参数
- aaa 的值 是有 next( )传入的参数决定.
疑问, 可以看出, createIterator 函数内部, 是无法实现指针的移动的,
需要靠 实例的next, 我们有没有可能让开启一个就自动往下移动指针执行?
通过 上面的参数通道, 我刚开始以为是可以的.
function *createIterator () {
let aaa;
console.log('a');
aaa = yield 1;
aaa.next(aaa);
console.log('b');
aaa = yield 2;
console.log('c');
aaa.next(aaa);
aaa = yield 3;
console.log('d');
aaa.next(aaa);
}
let process = createIterator();

不行, 逻辑上就有问题, 问题在于, 我的指针需要从1 到达2
但是在到达2之前,又开启了一个指针移动命令, 从逻辑上就有问题.
function *createIterator () {
let aaa;
console.log('a');
aaa = yield 1;
setTimeout(function () {
aaa.next(aaa);
},0)
console.log('b');
aaa = yield 2;
console.log('c');
setTimeout(function () {
aaa.next(aaa);
},0)
aaa = yield 3;
console.log('d');
setTimeout(function () {
aaa.next(aaa);
},0)
}
let process = createIterator();
process.next(process);
process.next(process);
用setTimeout 勉强能够实现这个内部自动指针向下走,
下面这个例子, 丁老师突然间秀的当真是令人措手不及.
function first () {
console.log('first ing');
setTimeout(function () {
console.log("first");
},1000)
}
function second () {
console.log('second ing');
setTimeout(function () {
console.log("second");
},2000)
}
function third () {
console.log('third ing');
setTimeout(function () {
console.log("third");
},1000)
}
function *createIterator () {
let result = `${yield first()}${yield second()}${yield third()}`;
console.log(result);
}
let process = createIterator();

可以看到
- 再次可以看到第一次执行next 的时候, 是无法接收传入的参数的.
- 这里模板字符串用得非常的6
- 用模板字符串接受了,传进来的参数
- 在模板字符串的变量区里直接定义了代码,
- , 读到 ${yield first()}的时候, 尽管在字符串模板里, 他也正常停止了.
- 丁老师秀的我确实感觉这就是另一门语言..
疑问? 不是模板字符串, 而是普通字符是否也可以?
function *createIterator () {
// let result = `${yield first()}${yield second()}${yield third()}`;
let result = '' + (yield first()) + (yield second()) + (yield third());
console.log(result);
}

委托迭代器
function *pro (arr,string) {
yield * arr;
yield * string;
}
let i = pro([1,2,3,4],'abcdef');

function *pro (arr,string) {
yield * arr;
yield * string;
}
let i = pro([1,2,3,4],'abcdef');
for(let key of i){
console.log(key);
}

可以理解为
- 把arr[Symbol.iterator] 里的指针和 arr[Symbol.iterator]的指针合在了一起?
网友评论