为什么会写这个?
同事的疑问
let a = []
a[5] = 1
console.log(a.length)
a.forEach(function(item) {
console.log(item);
});
结果是这样的
按理来说,不是应该循环6次的么,是不是循环的方法不对,我们试试用其他的
一样,我们看一下a是怎样的
可以看到,长度为6,但是前面5个全是空,也就是说,会跳过空的内容,Google了一番,找到这篇文
里面提到一个点就是,什么都没有的数组元素叫做槽(slot),一般方法都会忽略,还说到了关于V8源码里面对于数组的定义
function ArrayMap(f, receiver) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f);
var result = ArraySpeciesCreate(array, length);
for (var i = 0; i < length; i++) {
if (i in array) {
var element = array[i];
%CreateDataProperty(result, i, %_Call(f, receiver, element, i, array));
}
}
return result;
}
可以看到里面有一句if (i in array),也就是我们创建数组的时候,只是一个指针,如果没有内容,是不会实际创建的
如果我们是在需要创建一个空的数组,又需要循环,我们可以采取这种方法
Array.from(new Array(4))
可以看到,循环生效了,虽然结果是undefined
数组的循环
由此,就引发了我一个想法,js里面的数组的循环,或者说遍历分别有哪些呢
1. for-in
var a = [1, 2, 3];
for (var i in a) {
console.log(a[i]);
}
// 1
// 2
// 3
但是其实for-in不是很好,因为for-in 循环遍历的是对象的属性,而不是数组的索引。因此, for-in 遍历的对象便不局限于数组,还可以遍历对象
<script>
var a = [1, 2, 3];
a.foo = true
console.log(a)
for (var i in a) {
console.log(a[i]);
}
</script>
所以不建议用,并且我们遍历的是对象,它的顺序也是不固定的,很奇怪
<script>
let person = {
fname: "san",
lname: "zhang",
age: 99,
1: 2
};
let info;
for (info in person) {
console.log("person[" + info + "] = " + person[info]);
}
</script>
可以看到,key为1的先出来的,关于这个其实也有讨论,在这里就不展开了,有兴趣可以去看这篇文,一句话概括就是
先遍历出整数属性(integer properties,按照升序),然后其他属性按照创建时候的顺序遍历出来。
2.数组自带的遍历?
其实数组自身就有很多遍历可供我们使用
- Array.prototype.forEach数组对象内置方法
- Array.prototype.map数组对象内置方法
- Array.prototype.filter数组对象内置方法
- Array.prototype.reduce数组对象内置方法
- Array.prototype.some数组对象内置方法
- Array.prototype.every数组对象内置方法
- Array.prototype.indexOf数组对象内置方法
- Array.prototype.lastIndexOf数组对象内置方法
(1) forEach
php里面最常用
var a = [1, 2, 3];
a.forEach(function (value, key, arr) {
console.log(value) // 结果依次为1,2,3
console.log(key) // 结尾依次为0,1,2
console.log(arr) // 三次结果都为[1,2,3]
})
这个方法就只是单纯的循环,通过回调函数来提取你需要的数据,处理你的业务逻辑
map
map这个方法可以通过递归,然后在回调里面return一个新的内容,用这一些信息的内容组成一个新的数组来返回给你,而且并不会改变原来的数组结构
<script>
var a = [1, 2, 3];
var b = a.map(function (value, key, arr) {
console.log(value) // 结果依次为1,2,3
console.log(key) // 结尾依次为0,1,2
console.log(arr) // 三次结果都为[1,2,3]
return value + 1;
})
console.log(a); // 结果为[ 1, 2, 3 ]
console.log(b); // 结果为[ 2, 3, 4 ]
</script>
很好用,特别在对于结合我们vue的时候特别有用,很多时候我们在拿到后端数据的时候然后做数据绑定的时候,需要对数组做加工,例如,我们这个数组给表格用的,我们的表格有可能会有一些额外需要显示的属性
<script>
var a = [{
x: 100,
y: 200
}, {
x: 500,
y: 200
}];
var b = a.map(function (value, key, arr) {
return {
...value,
z: value.x + value.y
}
})
console.log(a)
console.log(b)
</script>
(2) filter(过滤器)
顾名思义,这是一个给我们过滤的方法,直接上代码
var a = [1,2,3];
var b = a.filter(function(value,key,arr){
console.log(value) // 结果依次为1,2,3
console.log(key) // 结尾依次为0,1,2
console.log(arr) // 三次结果都为[1,2,3]
if(value === 3){
return false;
}
return true;
})
console.log(a); // 结果为[ 1, 2, 3 ]
console.log(b); // 结果为[ 1,2 ]
在回调里面如果我们返回了true,这个就意味着,我们要,如果是false就是说这个我们不要,最后组成一个新的数组,返回给你,并不会影响原数组
(3) reduce(减少)
英文名字是减少,但是更多我们使用的时候其实是累加,一般用于数组里面数据的累加或者组合
<script>
var num = [1, 2, 3, 4, 5];
var res = num.reduce(function (total, num, index, arr) {
console.log('现在是第' + index + '个' + 'total的值为:' + total + '====num的值为' + num)
return total + num;
})
console.log(res)
</script>
在这里我们需要注意一个点,我们递归竟然是从index为1开始,而且这个时候,第二个参数num,是数组里面第一个的值,其实很好理解,因为我们里面拿到的第一个参数是数组的上一个循环里面返回的,数组第一个压根就没有上一个,所以就直接跳过了第一个,但是有的时候我们需要也操作第一个怎么办,可以这样
<script>
var num = [1, 2, 3, 4, 5];
var res = num.reduce(function (total, num, index, arr) {
console.log('现在是第' + index + '个' + 'total的值为:' + total + '====num的值为' + num)
return total + num;
}, 10)
console.log(res)
</script>
这个方法平时我们用得比较少,其实很有用的,例如合并二维数组
<script>
var red = [
[0, 1],
[2, 3],
[4, 5]
].reduce(function (a, b) {
return a.concat(b);
}, []);
console.log(red)
</script>
(4) find和some和every
find这个方法顾名思义就是找到数组里面合适我们条件的那一个
<script>
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
var ret1 = arr1.find((value, index, arr) => {
return value > 4
})
var ret2 = arr1.find((value, index, arr) => {
return value > 14
})
console.log('%s', ret1)
console.log('%s', ret2)
</script>
找不到就undefined
some和find十分相似,也是找到有符合条件,不过,返回的是一个布尔值
<script>
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
var ret1 = arr1.some((value, index, arr) => {
return value > 4
})
var ret2 = arr1.some((value, index, arr) => {
return value > 14
})
console.log('%s', ret1)
console.log('%s', ret2)
</script>
而every就是找到有一个不符合条件的就返回true
这里你可以有疑问,其实上面的map等方法我们都可以找到.为什么我们要用这个,因为几个方法有一个好的地方,就是找到了以后,不会继续执行下去,而map等方法是会继续的
3.for-of
上面的方法都很不错,都很好,但是他们都存在同一个问题
不能正确响应 break, continue, return
就是说,我们不能人为的中途干预说我不想继续循环了,所以es6就引入乱for-of
回到我们一开始使用for-in的例子,替换成for-of,可以看到,成功执行了
<script>
var a = [1, 2, 3];
a.foo = true
console.log(a)
for (info of a) {
console.log(info)
}
</script>
但是这个方法也有一个问题,拿不到index,这个时候怎么办,其实这个方法不单单可以循环数组,只要具备Iterator接口的都可以,我们可以这样
<script>
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
const arrIte = arr.entries()
for (const [key, val] of arrIte) {
console.log('key=>' + key + ' val=>' + val)
}
</script>
总结
那么,说了那么多,到底我们应该用哪个循环比较好?其实这是一个比较开放性的问题,因为单纯轮性能来说,for的效率的最高的
但是很多时候,我们在业务的操作用,单单用for的话,我可能得额外又定义一些变量来帮助我计算出最好的结果,而且for的可读性并没有那一堆map/some等等的高,所以个人建议,在数据量不大的情况下尽量使用map/forEach等方法,需要循环的数据特别大的情况下可以酌情使用for来执行