再探js数组遍历
今天又看了一遍js高程的数组迭代方法,看到书中介绍ECMA5中的5个迭代方法都不会改变数组中包含的值。然后突然想到平时在做业务的时候经常有从接口拿数据,数据拿过来就一个遍历处理,然后遍历后的数组在渲染到页面。诶,按照这个习惯,数组的值似乎是被改变了呢!
干想也想不通,还是到浏览器下来输出下结果会比较直观。
var arr = [1, 2, 3];
arr.forEach(function(v) {
v += 1;
});
console.log(arr); //[1, 2, 3]
咦?似乎是不变的呢!好像平时都在处理对象数组吧?好,那就上对象数组!
var arr = [
{a: 1},
{a: 2},
{a: 3}
];
arr.forEach(function(v) {
v.a += 1;
});
console.log(arr); // [{a: 2},{a: 3},{a: 4}]
这就尴尬了!对象数组内的属性值都变了!但js高程对迭代方法的讲解篇幅比较短,也没有作深入讨论,好吧,去看下源码实现吧。
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
k = 0;
while (k < len) {
var kValue;
if (k in O) {
kValue = O[k];
callback.call(T, kValue, k, O);
}
k++;
}
};
}
callback.call(T, kValue, k, O)
每次回调会把对应的数组项传参进去,诶,传参,我似乎明白了。之前写过一个js传参的方式,还是印象深刻。
js是按值传递参数的,或者按共享传参的说法去理解,当传递的是基本数据类型时,复制一份给参数,回调里面对参数做了什么,外面的数组项还是原来的值。
而如果传递的是引用类型参数,也是会复制一份对象作为参数,但这时候的参数和回调外的数组项指向的是同一个堆内存,好了,回调对它做了什么,外面的数组项也反应了出来。
数组遍历的话题又回到了函数传参问题!
我又去试了其他几个遍历方法,当用every时情况又有所不一样。
var arr = [
{a: 1},
{a: 2},
{a: 3}
];
arr.every(function(v) {
v.a += 1;
});
console.log(arr); // [{a: 2},{a: 2},{a: 3}]
再去看书对every()方法的解释:对数组的每一项运行给定的函数,如果该函数对每一项都返回true,则返回true。
看完,我猜想这个every的实现应该是遍历每一项,只要有一项没有返回true就会直接中断函数,返回false。
还是去看看源码(易读版):
Array.prototype.every = function(fun /*, thisp*/) {
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this && !fun.call(thisp, this[i], i, this))
return false;
}
return true;
};
再回头看看我写的js里面,只对每项的a属性值作了+1处理,没有任何返回值,那么js函数在没有返回值的时候,会如何处理返回值呢?
再来看个小实验:
function returnT(a) {
a += 1;
return true;
}
function returnNull(a) {
a += 1;
}
console.log(!!returnT(1)); // true
console.log(!!returnNull(1)); // false
也就是说,当函数没有任何返回值的时候,函数会默认返回false;
ok,再结合every的源码一看,秒懂。