在ES6的教程里先说了Map和Set再说Iteratator,尽管我们理解遍历的意思是什么,但是学习Iterator的原理和结构有助于我们进一步理解有应用到遍历器的语法。
Iterator原理
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
默认的Iterator
存在这样的一个默认接口:如果一个数据结构拥有属性Symbol.iterator,则这个数据结构是可遍历的。属性Symbol.iterator对应一个函数,该函数规定了遍历的规则。执行这个函数,就会返回一个遍历器。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
该例子中常量obj是可遍历的,因为它拥有Symbol.iterator属性,该属性对应一个函数,函数返回一个拥有next属性(对应一个返回value和done的函数)的对象。所以当我们调用obj.next()的时候,就会返回对应的value和done的值。
原生Iterator
以下是原生拥有Iterator接口的数据结构:
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署(部署Symbol.iterator的函数,还有next函数),这样才会被for...of循环遍历。部署还有一种取巧的方法,就是类似数组的对象可以直接借用Array.prototype[Symbol.iterator]。
return方法和throw方法
return方法和throw方法在部署中是可选的。
return方法针对以下情况执行:
在for...of循环中,
1.出错 2.break语句 3.continue语句
for...of和for...in
for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值。
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
iterator的使用
iterator主要应用于Generator,但在此之前还要温习一下具备原生接口的Set和Map。
Set和weakset
Set
创建Set
const set = new Set()
也可以传入数组初始化set
const set = new Set([1,2,3,4])
set里面没有重复的值,传入重复的值会被排除掉。
Set 实例的属性和方法
Set 结构的实例有以下属性。
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
操作方法:
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。
遍历方法:
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
Weakset
const ws = new WeakSet();
WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。
WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在
WeakSet 没有size属性,没有办法遍历它的成员。
Map和weakmap
Map
Map是一种具有键值对的数据结构,相对于Object来说,它的优点是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串-值”的对应,Map 结构提供了“值-值”的对应。
const m = new Map();
const o = {p: 'Hello World'};
//Map { { p: 'hello world' } => 'content' }
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
赋值应用:任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
如果对同一个键多次赋值,后面的值将覆盖前面的值。
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
上面代码对键1连续赋值两次,后一次的值覆盖前一次的值。
如果读取一个未知的键,则返回undefined。
new Map().get('asfddfsasadf')
// undefined
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
实例属性和方法
1.size
属性返回 Map 结构的成员总数。
2.set(key, value) set方法返回的是当前的Map对象,因此可以采用链式写法。
3.get(key) 如果找不到key,返回undefined。
4.has(key)
5.delete(key)删除某个键,返回true。如果删除失败,返回false
6.clear()清除所有成员,没有返回值。
遍历方法:
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。
Weakmap
首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
垃圾回收机制以后再写一篇笔记说明。
WeakMap只有四个方法可用:get()、set()、has()、delete()。
没有遍历机制和size属性。
Generator
终于讲到了Generator。
Generator可以看作是一个封装了多个内部状态的函数,这些状态是可遍历的。执行Generator返回一个遍历器。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
注意的地方:
1.yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
2.yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
3.yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
next()带参数
next方法的参数表示上一个yield表达式的返回值(所以在第一次使用next方法时,传递参数是无效的)
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
for...of
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
从例子可以看出,当done为true时,for..of停止遍历并且不返回当前值。
除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
return()和throw()
throw方法可以接受一个参数,该参数会被catch语句接收。
在generator内如果有try...catch,那么generator的错误会被捕捉到一次。当内部已经捕获到错误,后继的错误就由外部处理或者直接中断程序。
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
return方法,可以返回给定的值,并且终结遍历 Generator 函数。如果return方法调用时,不提供参数,则返回值的value属性为undefined。
generator的this指向哪里
如果不作任何处理,在generator实例内定义一个this.a=1,this指向的是全局。
如果希望generator可以兼备next方法和正常的this指向,有两个方法
1,使用call或者apply绑定一个空对象
2,绑定generator函数的prototype