本想就针对for..of看下,结果拔出萝卜带出泥,ES6相关扯出一大堆,感觉有必要记录。
1、for...of只会遍历value
2、for...of比单纯的for循环小巧,不需要写一堆变量和++,比forEach灵活,因为它可以使用break等关键字
这是我从网上搜集来的对for..of的说法
第一个,如果是只真对于数组来说,就算对吧,(因为我们一般也不会在原型方法上做手脚)如果是其他的可迭代对象,比如 Map,循环出来的是这样的[key,value],得清楚,for...of不是专为数组预备的,以下会说明。
第二个,嗯,还算靠谱。不过貌似还是针对数组来说的,一定要脱离数组看for...of
开始,看个例子
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
let iterable = [3, 5, 7];
iterable.foo = "hello";
for (let i in iterable) {
console.log(i);
// 0, 1, 2, "foo", "arrCustom", "objCustom"
}
console.log(Object.keys(iterable))
//[ '0', '1', '2', 'foo' ]
for (let i of iterable) {
console.log(i);
// 3, 5, 7
}
这个例子除了展示for...of与for...in的区别,也很好的说明,千万不要项目中去给内置构造函数的原型对象添加方法和属性,产生的副作用非常严重,不要给你的队友制造麻烦。
如果你非要添加,参考以下几点
1.和你的队友协商好。
2.把你添加的属性方法设置成不可枚举。
3.你能保证你定义的属性名日后不会被ECMA定义。
4.你很清楚在指定工程中这样做会产生的可预见的副作用(很难)。
for...of可迭代对象(包括 Array,Map,Set,String,TypedArray[描述了一个底层的二进制数据缓冲区],arguments 对象等等)
for...of可以迭代伪数组,为什么呢?用nodeList 和 arguments 举例
他们都有一个Stmbol.iterator的属性,但是ES6之前这些伪数组可是没有这个玩意的,添加Stmbol.iterator属性就是为了让伪数组支持for...of遍历,不只是伪数组,Array,Map,Set,String,TypedArray,这些同样具备Stmbol.iterator属性。
对象具备Stmbol.iterator属性被称为遵守可迭代协议,for...of语句只会遍历遵守可迭代协议的对象。
Stmbol.iterator这个值有点奇葩
查看MDN解释
Symbol.iterator 属性的属性特性:
writable false //不可修改值
enumerable false //不可枚举
configurable false //不可以使用delete删除
先测试内置对象中自带Stmbol.iterator的对象是否具备以上的特性:
var pro = Array.prototype
Object.getOwnPropertyDescriptor(pro, Symbol.iterator)
{writable: true, enumerable: false, configurable: true, value: ƒ}
只有一个不可枚举特性与上相符,其他都不相符。
自定义添加的Stmbol.iterator问题更大点
var a = {[Symbol.iterator]:1}
console.log(Object.getOwnPropertyDescriptor(a, Symbol.iterator))
{value: 1, writable: true, enumerable: true, configurable: true}
很明显,自定义添加的Symbol.iterator三个修饰符都是true
但是经过实测,结论如下
1、自定义添加的Stmbol.iterator属性值可以修改
2、自定义添加的Stmbol.iterator属性可以删除
3、重点:自定义添加的Stmbol.iterator属性虽然enumerable描述符为true,代表可以枚举,但是,我使用Object.keys和for...in都无法遍历出来,即使使用Object.getOwnPropertyNames 也没个卵用【Chrome 和 Firefox测试效果一致】
所以,如果你想添加自定义Stmbol.iterator,最好手动添加三个描述符的值为false。
无法使用for...of直接遍历对象
var myIterator = {}
for(let i of myIterator){ console.log(i)}
//Uncaught TypeError: myIterator is not iterable
添加Symbol.iterator后,使用for...of直接遍历对象,依然报错
var myIterator = {
[Symbol.iterator]: function() { return 1 }
}
for(let i of myIterator){ console.log(i)}
//Result of the Symbol.iterator method is not an object
根据上图可以发现,Stmbol.iterator这个属性的值为一个函数,这个函数同样要遵守一个协议,叫做迭代器协议。否则就会出现报错,迭代器的两种写法:
1、可以是自定义函数,他的写法如下:
(下面会具体实现,先过一遍)
var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
}
2、也可以是生成器函数,他的写法如下:
(下面会具体实现,先过一遍)
var myIterator = {
[Symbol.iterator]: function*() {
yield 1;
yield 2;
yield 3;
}
}
**其他问题
问:生成器对象到底是一个迭代器,还是一个可迭代对象?
答:生成器对象既是迭代器,也是可迭代对象
var aGeneratorObject = function*(){
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
Ⅰ. "function",因为它有next方法,所以它是一个迭代器
typeof aGeneratorObject[Symbol.iterator];
Ⅱ. "function",因为它有@@iterator方法,所以它是可迭代的
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
Ⅲ. true,因为它的@@iterator方法返回自己(一个迭代器),所以它是一个格式良好的迭代器
Ⅰ. 和 Ⅱ.是(可用的)可迭代对象的充要条件;
Ⅲ. 是判断一个(可用的)可迭代对象的迭代器格式是良好的,还是不良好的方法;
可以通过给自定义对象添加迭代器,使它能够使用for...of遍历。
需求:
var a = {a:1, b:2}
使
for(let i of a){
console.log(i)
}
输出
//['a', 1]
//['b', 2]
方法一:自定义函数方式添加
const setIterator = function (obj = {}){
// 这样添加保证writable,enumerable,configurable都为false
let pro = Object.defineProperty({}, Symbol.iterator, {
value: function(){
let index = 0;
let propKeys = Object.keys(this);
return {
next: function (){
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return {
value: [key, this[key]],
done: false
}
} else {
return { done: true }
}
}.bind(this)
}
}
});
console.log(pro[Symbol.iterator]() === pro) //false
//因为值为false,所以这不是一个良好的迭代器格式
//但是不影响它使用
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
将方法一改成良好的迭代器写法
const setIterator = function (obj = {}){
let index = 0;
let pro = Object.defineProperties({}, {
next: {
value(){
let propKeys = Object.keys(this);
if (index < propKeys.length) {
let key = propKeys[index];
index ++;
return {
value: [key, this[key]],
done: false
}
} else {
index = 0;
//使用完记得把变量归0,以便下次遍历
return { done: true }
}
}
},
[Symbol.iterator]: {
value(){ return this }
}
});
console.log(Object.getOwnPropertyDescriptors(pro))
console.log(pro[Symbol.iterator]() === pro) // true
//值为true,所以这是一个良好的迭代器格式
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
**备注:改成良好的迭代器格式没有什么实际意义,只为展示。
方法二:函数生成器方式添加
const setIterator = function (obj = {}){
let pro = Object.defineProperty({}, Symbol.iterator, {
value: function * (){
let index = 0;
let propKeys = Object.keys(this);
while (index < propKeys.length){
let key = propKeys[index];
index ++;
yield [key, this[key]]
}
}
});
console.log(pro[Symbol.iterator]() === pro) // false
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2, c:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
**关于函数生成器
这个例子很能说明问题,具体原文 https://www.jianshu.com/p/36c74e4ca9eb
这个例子很能说明问题,具体参见 https://www.jianshu.com/p/36c74e4ca9eb
function* test(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
console.log(`x=${x}, y=${y}, z=${z}`)
return (x + y + z)
}
var a = test(5);
console.log(a)
console.log(a.next())
console.log(a.next())
console.log(a.next())
//Object [Generator] {}
//{ value: 6, done: false }
//{ value: NaN, done: false }
//x=5, y=NaN, z=undefined
//{ value: NaN, done: true }
var b = test(5);
console.log(b)
console.log(b.next())
console.log(b.next(12))
console.log(b.next(13))
//Object [Generator] {}
//{ value: 6, done: false }
//{ value: 8, done: false }
//x=5, y=24, z=13
//{ value: 42, done: true }
能够接受可迭代对象作为参数的API
new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
能够处理 可迭代对象 的语句和表达式有:
for...of 循环、展开语法、yield*,和结构赋值。
yield* 参照https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*
for(let value of ["a", "b", "c"]){
console.log(value);}// "a"// "b"// "c"
[..."abc"]; // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];}
gen().next(); // { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
a // "a"
...展开语法 , new Set , new Map
展开语法:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax
var a1 = [1, 2, 3, 4]
var o1 = {a: 1, b: 2}
console.log([...a1]) // [ 1, 2, 3, 4 ]
//在Areay内扩展Array,正常
console.log({...a1}) // { '0': 1, '1': 2, '2': 3, '3': 4 }
//在Object内扩展Array,正常
console.log({...o1}) // { a: 1, b: 2 }
//在Object内扩展Object,正常
console.log([...o1]) // o1 is not iterable
//在Array内扩展Object,报错
如果要在Array内使用扩展运算符,被扩展的对象必须是可迭代对象。
初始化自定义对象{}不具备迭代器,所以不能在Array内...展开,
除非是手动定义迭代器,
//这个是上面我定义的函数,再此就直接使用了。
var myIterable = setIterator({a:1, b:2})
[...myIterable ] = [['a', 1], ['b', 2]]
备注:即使你为一个object包装成一个可迭代对象(如上),
在 {...myIterable} 时,得到的也是 {a:1, b:2},自定义的迭代器
不会影响object在object内的...默认展开。
可以把伪数组,转化成数组,如下
var div = document.getElementsByTagName('div')
[...div] // [div, div, div.....]
!function (){
console.log([...arguments]) //[1, 2, 3]
}(1,2,3)
作为函数形参和实参时的区别,如下
function test(a, b, c){
console.log(a) //1
console.log(b) //2
console.log(c) //3
}
!function (...arg){
console.log(arg) //[1, 2, 3]
test(...arg)
}(1,2,3)
new Set:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set
Set翻译为集合或集。
对象允许你存储任何类型的唯一值,
无论是原始值或者是对象引用。
目前我知道的作用就是去重。
测试前须知:
let n1 = NaN, n2 = n1
console.log(n1 == n2) // false
console.log(-0 === 0) // true
测试:
let a = {},
b = a,
c = {},
d = function(){},
e = d,
f = NaN,
g = f,
h = -0,
i = 0;
let mySet = new Set([a, b, c, d, e, f, g, h, i])
console.log(mySet) // Set { {}, {}, [Function: d], NaN, 0 }
console.log([...mySet]) // [ {}, {}, [Function: d], NaN, 0 ]
结论:他能去重NaN,但不能区分0和-0
new Map:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map
Map翻译为映射。
测试前须知:
数组与对象也都可以看成映射,只不过对象的key只能是字符串和Symbol
数组的key只能是正整数的字符串
【数组的下标也是字符串,使用Array[0],是先把0转化成字符串】
但是Map就没这个限制了,他的key可以是任何数据类型
测试:
let arr1 = [a1 = new Function, a2 = {}, a3 = [], a4 = NaN, a5 = new Map]
let arr2 = [b1 = 1, b2 = [], b3 = new Function, b4 = 3, b5 = 'abc']
let myMap = new Map;
arr1.forEach((item, i) => {
myMap.set(item, arr2[i])
})
console.log(myMap.get(a1) === b1) //true
console.log(myMap.get(a2) === b2) //true
console.log(myMap.get(a3) === b3) //true
console.log(myMap.get(a4) === b4) //true
console.log(myMap.get(a5) === b5) //true