1.for in (可遍历对象和数组,不建议遍历数组)
1.1使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问,
Object.prototype.say = "hello"; //修改Object.prototype
var person = {age:18};
for(var key in person){
console.log(key,person[key]);///循环时用person.key得不到对象key的值,可用person[key] 或者 eval("person."+key);
}
//test.html:31 age 18
//test.html:31 say hello
/
Array.prototype.say = "u";
var children = [1,3,"djn"];
for(var key in children){
console.log(key,children[key]);
}
//test.html:37 0 1
//test.html:37 1 3
//test.html:37 2 djn
//test.html:37 say u
1.2.只遍历对象自身的属性,而不遍历继承于原型链上的属性,使用hasOwnProperty 方法过滤一下。
Object.prototype.say = "hello";
var person = { age: 18 };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key, person[key]);
}
}
// age 18
1.2 Object.keys(obj) 方法会返回一个由给定对象的自身可枚举属性组成的数组
数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环会枚举其原型链上的属性)。返回值是这个对象的所有可枚举属性组成的字符串数组。
Object.keys
返回一个所有元素为字符串的数组,其元素来自于从给定的object
上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
// object对象没有length属性,可以通过Object.keys(person).length,来获取person的长度了。
Object.prototype.say = "hello";
var person = {age:18};
console.log(Object.keys(person));
//
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// for(var j of anObj){
// console.log(j);
// }//anObj is not iterable
// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
getFoo: {
value: function () { return this.foo; }
}
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']
针对for in 和 Object.keys()循环遍历的顺序,我觉得有必要追究一下,就查了些相关资料。其循环对象时,顺序不可靠,但是他是依据什么顺序来遍历的呢,我找到一篇文章总结了一句话:先遍历证书属性(按照升序),然后根据其他属性先后创建顺序遍历出来的。
案例如下:
// array like object with random key ordering
var anObj = { name: "nn", 100: 'a', 2: 'b', 7: 'c', age: "kk" };
console.log(Object.keys(anObj)); // console: ["2", "7", "100", "name", "age"]
for (var i in anObj) {
console.log(i);
}
// test.html: 74 2
// test.html: 74 7
// test.html: 74 100
// test.html: 74 name
// test.html: 74 age
// test.html: 74 say
2. for of (ES6)
for...of
语句在可迭代对象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句. 不可迭代一个普通对象
但是可以循环一个拥有enumerable属性的对象。n如果我们按对象所拥有的属性进行循环,可使用内置的Object.keys()方法
let obj = { a: '1', b: '2', c: '3', d: '4' }
for (let o of Object.keys(obj)) {
console.log(o)
}
// a
// b
// c
// d
如果我们按对象所拥有的属性值进行循环,可使用内置的Object.values()方法
let obj = { a: '1', b: '2', c: '3', d: '4' }
for (let o of Object.values(obj)) {
console.log(o)
}
// test.html:103 1
// test.html:103 2
// test.html:103 3
// test.html:103 4
let iterable = [10, 20, 30];
for (let value of iterable) {
value += 1;
console.log(value);
}
// 11
// 21
// 31
禁止修改 value 的值,可以将let 改为 const (不可修改的常数)
let iterable = [10, 20, 30];
for (const value of iterable) {
console.log(value);
}
// 11
// 21
// 31
//迭代 String
let iterable = "boo";
for (let value of iterable) {
console.log(value);
}
// "b"
// "o"
// "o"
//迭代TypeArray
let iterable = new Uint8Array([0x00, 0xff]);
for (let value of iterable) {
console.log(value);
}
// 0
// 255
//迭代Map
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (let entry of iterable) {
console.log(entry);
}
// ["a", 1] ===> 0:"a",1:"1"
// ["b", 2] ===> 0:"b",1:"2"
// ["c", 3] ===> 0:"c",1:"3"
for (let [key, value] of iterable) {
console.log(key,value);
}
// a 1
// b 2
// c 3
迭代set
let iterable = new Set([1, 1, 2, 2, 3, 3]);
for (let value of iterable) {
console.log(value);
}
// 1
// 2
// 3
/迭代arguments对象
(function() {
for (let argument of arguments) {
console.log(argument);
}
})(1, 2, 3);
// 1
// 2
// 3
关闭迭代器:
对于for...of的循环,可以由break,throw,continue 或者 return 终止。在这些情况下,迭代器关闭。
function* foo() {
yield 1;
yield 2;
yield 3;
};
for (let o of foo()) {
console.log(o);//1
break; // closes iterator, triggers return
}
function*
这种声明方式(function
关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator
对象。
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器(iterator )对象。当这个迭代器的 next()
方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield
的位置为止,yield
后紧跟迭代器要返回的值。或者如果用的是 yield*
(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
next()
方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield
表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield
语句,即生成器函数是否已经执行完毕并返回。
调用 next()
方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值,例如:
function *gen(){
yield 10;
y=yield 'foo';
yield y;
}
var gen_obj=gen();
console.log(gen_obj.next());// 执行 yield 10,返回 10
console.log(gen_obj.next());// 执行 yield 'foo',返回 'foo'
console.log(gen_obj.next(10));// 将 10 赋给上一条 yield 'foo' 的左值,即执行 y=10,返回 10
console.log(gen_obj.next());// 执行完毕,value 为 undefined,done 为 true
不要重用生成器
生成器不应该重用,即使for...of
循环的提前终止,例如通过break
关键字。在退出循环后,生成器关闭,并尝试再次迭代,不会产生任何进一步的结果。
var gen = (function *(){
yield 1;
yield 2;
yield 3;
})();
for (let o of gen) {
console.log(o);
break;//关闭生成器
}
//生成器不应该重用,以下没有意义!
for (let o of gen) {
console.log(o);
}
for in 和 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"
}
for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i); // 0, 1, 2, "foo"
}
}
for (let i of iterable) {
console.log(i); // 3, 5, 7
}
// 不可迭代一个普通对象
Object.prototype.say = "hello";
var person = { age: 18 };
for (var key of person) {
console.log(key, person[key]);
}
//Uncaught TypeError: person is not iterable
每个对象将继承objCustom
属性,并且作为Array
的每个对象将继承arrCustom
属性,因为将这些属性添加到Object.prototype
和Array.prototype
。由于继承和原型链,对象iterable
继承属性objCustom
和arrCustom
。
for in 循环仅以原始插入顺序记录iterable
对象的可枚举属性。它不记录数组元素3
, 5
, 7
或hello
,因为这些不是枚举属性。但是它记录了数组索引以及arrCustom
和objCustom
。
使用hasOwnProperty()
来检查,如果找到的枚举属性是对象自己的(不是继承的)。如果是,该属性被记录。记录的属性是0
, 1
, 2
和foo
,因为它们是自身的属性(不是继承的)。属性arrCustom
和objCustom
不会被记录,因为它们是继承的。
for of 循环迭代并记录iterable
作为可迭代对象定义的迭代值,这些是数组元素 3
, 5
, 7
,而不是任何对象的属性。
3. foreach array.forEach(element => { });
方法对数组的每个元素执行一次提供的函数。
* forEach
方法按升序为数组中含有效值的每一项执行一次callback
函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。
callback
函数会被依次传入三个参数:
- 数组当前项的值
- 数组当前项的索引
- 数组对象本身
function logArrayElements(element, index, array) {
console.log('a[' + index + '] = ' + element, array);
}
// 注意索引 2 被跳过了,因为在数组的这个位置没有项
[2, 5, , 9].forEach(logArrayElements);
// logs:
// a[0] = 2 [2, 5, , 9]
// a[1] = 5 [2, 5, , 9]
// a[3] = 9 [2, 5, , 9]
* forEach
遍历的范围在第一次调用 callback
前就会确定。调用 forEach
后添加到数组中的项不会被 callback
访问到。如果已经存在的值被改变,则传递给 callback
的值是 forEach
遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()
),之后的元素将被跳过.
var words = ["one","two","three","four"];
words.forEach((i)=>{
console.log(i);
if(i === "two"){
words.shift();
}
})
下面的例子会输出"one", "two", "four"。当到达包含值"two"的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 "four"现在在数组更前的位置,"three"会被跳过。 forEach()
不会在迭代之前创建数组的副本。
for in 和for of 也有此类特性
let iterable = [10, 20, 30];
for (const value in iterable) {
if(value == 1){
iterable.shift();
}
console.log(value);
}
// 0
// 1
//
let iterable = [10, 20, 30];
for (const value of iterable) {
if(value == 10){
iterable.shift();
}
console.log(value);
}
// 10
// 30
forEach(数组):
缺点:不能同时遍历多个集合,在遍历的时候无法修改和删除集合数据,方法不能使用break,continue语句跳出循环,或者使用return从函数体返回,对于空数组不会执行回调函数
优点:便利的时候更加简洁,效率和for循环相同,不用关心集合下标的问题,减少了出错的效率
for in (适用于对象,数组不建议):
缺点:某些情况下,会出现随机顺序的遍历,因为里面的值是string类型,增加了转换过程,因此开销较大
优点:可以遍历数组的键名,遍历对象简洁方便,可以使用break,continue和return
for of (数组,一般对象不可用,可借助Object.keys()/Object.value())
优点:避免了for in的所有缺点,可以使用break,continue和return,不仅支持数组的遍历,还可以遍历类似数组的对象,支持字符串的遍历最简洁,最直接的遍历数组的语法支持map和Set对象遍历
缺点:不适用于处理原有的原生对象(原生对象是一个子集,包含一些在运动过程中动态创建的对象)