之前在项目里遇到一个问题:
调用某个函数时,传过来的参数是动态的,不能预先估计会传来多少个,因此我需要遍历函数的arguments对象,用来获取传递来的所有参数。
因此最开始我写了如下的代码:

function test(){
    for(var i = 0;i<arguments.length;++i){
        console.log(arguments[i]);
    }
}

test(1,2,3);//打印出1 2 3

test(4,5,6,7);//打印出4 5 6 7

通过一个for循环,遍历arguments对象,就可以取到每次调用该函数时传递的所有参数了。

我们都知道因为js是没有的块级作用域的,因此上面的函数在使用for循环时,会向该函数的作用域中增加一个额外的变量i,如果在之后的代码中又使用了变量i,某些情况就会导致错误,因此最好使用js提供的循环方法来替代for循环,例如forEach。

于是我后来在重构这块代码的时候,就打算用forEach来替代for循环,于是我写下了如下的代码:

function test(){
    arguments.forEach(function(param,index){
        console.log(param);
    });
}

test(1,2,3);//Uncaught TypeError: arguments.forEach is not a function

然而却出现上述错误,告诉我arguments没有forEach这个方法。我很纳闷,js明明为数组提供了包括forEach,every,some,map等在内的一系列用于遍历的方法,但是为什么在这里会提示我forEach方法不存在呢?

除非arguments不是一个数组,于是我在控制台执行了这一行代码:

...test函数内
console.log(arguments instanceof Array);//false
...

这证明了arguments真的不是一个数组,但是不是数组怎么能通过for循环来遍历呢!这让当时的我苦恼了好一阵子。

直到我在stackoverflow上看到了伪数组array-like这个概念的介绍。

那么什么是伪数组?伪数组就是javascript通过一个实际上是对象但却能够被当做数组来使用的和数组具有相同特征的特殊对象。

例如:

var fakeArr = {
    0:'zero',
    1:'one',
    2:'two',
    3:'three',
    4:'four',
    5:'five',
    length:6
}

for(var i = 0;i<fakeArr.length;++i){
    console.log(fakeArr[i]);
}
//zero one two three four five

我定义了个特殊的对象,这个对象的每个key值都是一个正整数,除了最后一个key为length,length的值代表了这个对象中有几个元素。

再来看看下面的for循环,fakeArr.length用于控制循环结束,因为此时fakeArr.length值为6,因此循环将会从i=0执行到i=5。而fakeArr[i]则代表获取fakeArr对象中key为i的元素的值。

怎么样,是不是很眼熟了,fakeArr就像是一个伪装成为数组的对象,因为它和数组一样具有length属性,并且也能通过下标来使用访问符[]。

像fakeArr这样,每个key值都是一个正整数,且有length属性来表示该对象有多少个元素的对象,就被称作伪数组。

由于伪数组对象并不是真正的数组,它并没有和数组一样具有Array.prototype中定义的如forEach,map,indexOf,slice等在内的诸多方法。上文中的arguments就是一个伪数组对象,因此不能通过arguments调用forEach方法时将会报错。

但是这里我要介绍一个黑科技,一个可以使得伪数组也能使用数组原型中的方法的黑科技:

var fakeArr = {
    0:'zero',
    1:'one',
    2:'two',
    3:'three',
    4:'four',
    5:'five',
    length:6
}

Array.prototype.forEach.call(fakeArr,function(item,index){
    console.log(item);
});
//zero one two three four five

那就是call方法,通过call方法调用一个方法,将会使得方法内部this被赋值为call方法的第一个参数,因此上面的例子里,将fakeArr代替了原本应该是数组对象的this,而我猜测在forEach方法中也没有校验this对象是不是Array的实例,因此得以让伪数组fakeArr也能被当做数组对象来执行forEach方法。

forEach方法能够通过call的方法使得伪数组也能被遍历,但这并不代表数组原型中的其他方法也能用这种方式来使用,因为我也不知道数组原型中的方法的源码到底是怎么写的,还需要一个个的尝试才行。