之前在《JavaScript系列之this是什么》这篇文章中,我们曾谈过”this”这个关键字,本章将会谈到另一个JavaScript中的关键字,叫做”arguments”。

arguments到底是什么呢?下面就由我来简洁地介绍一下 arguments吧。

什么是参数(parameters)

在了解arguments之前,我们必须要先了解一下什么是参数(parameter)。参数其实就是我们会带入函数的变量,以下面例子来说,”house”、”car”、”money”,就是我们在执行函式的时候可以任意填入的参数。

function MyFavorite(house, car, money) {
console.log(house);
console.log(car);
console.log(money);
}

MyFavorite();

首先,当我建立好这样的函数,我可以不带任何参数值就去执行这个函数,只要输入MyFavorite()这样就可以了!

一般如果有参数却又没有给它参数值,函数的执行上往往会有错误!但在JavaScript中不太一样的地方在于,即使你没有给它任何参数值就加以执行,也不会报错,而是会返回undefined

为什么会得到”undefined“呢?

之所以会这样是因为当JavaScript在执行这个函数的时候,由于提升机制,它会先把我们的参数(house, car, money)存到内存中了,并且赋予它的值是undefined

参数值会由左至右读取,如果我依序执行这样的代码就理解了:

MyFavorite();
MyFavorite("别墅");
MyFavorite("别墅", "法拉利");
MyFavorite("别墅", "法拉利", "一亿元");

会分别读到以下的结果,表示JavaScript会由左至右来读取参数值,而且即使某些参数值有缺值的情况,JavaScript还是可以正常执行。

设置函数中参数的默认值

由于目前的几款浏览器使用的JavaScript版本都尚不支持直接在参数的地方设置默认值(ES6的将可以),所以很多的框架都还不会用这种方式设置默认值。

方法一:在ES6的JavaScript中,可以直接通过这种方式设置参数默认值:

function MyFavorite(house, car, money = '一亿元') {
console.log(house);
console.log(car);
console.log(money);
console.log("----------------");
}

方法二:利用强制转换的概念设置默认值

由于版本兼容的差异,现今多数的编程都是使用这种方式设置参数的默认值,利用简单的”=”和”||”就可以达到参数默认值的效果:

function MyFavorite(house, car, money) {

money = money || '一亿元';
console.log(house);
console.log(car);
console.log(money);
console.log("----------------");
}

这时候即使在没有给值的情况下,money一样可以得到默认值为”一亿元”:

类数组对象

了解了parameters的概念后,让我们回来谈谈arguments,MDN将它叫做类数组对象,那么什么是类数组对象呢?

所谓的类数组对象:

就是拥有一个 length 属性和若干索引属性的对象

举个例子:

var array = ['house', 'car', 'money'];

var arrayLike = {
0: 'house',
1: 'car',
2: 'money',
length: 3
}

//读取
console.log(array[0]); // house
console.log(arrayLike[0]); // house

array[0] = 'new house';
arrayLike[0] = 'new house';

//长度
console.log(array.length); // 3
console.log(arrayLike.length); // 3

//遍历
for(var i = 0, len = array.length; i < len; i++) {
……
}
for(var i = 0, len = arrayLike.length; i < len; i++) {
……
}

上面我们可以看得出来,类数组对象与数组在读取、获取长度、遍历三个方面一样,都能取到,那为什么还叫做类数组对象呢?

因为类数组对象不可以使用数组的方法,比如:

arrayLike.push('name');

然而上述代码会报错: arrayLike.push is not a function,所以终归还是类数组呐……

调用数组方法

如果类数组想用数组的方法怎么办呢?

直接调用是不可取的,那我们可以通过 Function.call 的方法进行间接调用:

var arrayLike = {0: 'house', 1: 'car', 2: 'money', length: 3 }

console.log(Array.prototype.join.call(arrayLike, '&')); // house&car&money

console.log(Array.prototype.slice.call(arrayLike, 0)); // ["house", "car", "money"]
// slice可以做到类数组转数组

Array.prototype.map.call(arrayLike, function(item){
return item.toUpperCase();
});
// ["HOUSE", "CAR", "MONEY"]

在上面已经提到了一种类数组转数组的方法,再补充三个:

var arrayLike = {0: 'house', 1: 'car', 2: 'money', length: 3 }
// 1. slice
console.log(Array.prototype.slice.call(arrayLike)); // ["house", "car", "money"]
// 2. splice
console.log(Array.prototype.splice.call(arrayLike, 0)); // ["house", "car", "money"]
// 3. ES6 Array.from
console.log(Array.from(arrayLike)); // ["house", "car", "money"]
// 4. apply
console.log(Array.prototype.concat.apply([], arrayLike));

接下来重点讲讲 Arguments 这个类数组对象。

arguments对象

arguments比起this来说,要容易理解的多,arguments对象只定义在函数体中,包括了函数的参数和其他属性。

同样地通过上面的例子加以理解,我们直接在函数中去打印出”arguments”这个关键字:

function MyFavorite(house, car, money) {

money = money || '一亿元';

console.log(arguments);
console.log("----------------");
}

MyFavorite();
MyFavorite("别墅");
MyFavorite("别墅", "法拉利");
MyFavorite("别墅", "法拉利", "一亿元");

打印结果如下:

我们可以看到除了类数组的索引属性和length属性之外,还有一个callee属性,而且我们可以看到arguments对象的__ proto __是指向object的,这也说明了他是个类数组对象,而不是一个数组。下面我们进行一一介绍。

length属性

arguments.length为函数实参个数,举个例子:

function foo(house, car, money){
console.log("实参的长度为:" + arguments.length)
}

console.log("形参的长度为:" + foo.length)

foo(1)

// 形参的长度为:3
// 实参的长度为:1

callee属性

每个参数实例都有一个callee属性,通过它可以调用函数自身。 ES5的严格模式不允许访问arguments.callee

讲个闭包经典面试题使用 callee 的解决方法:

var data = [];

for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i)
}).i = i;
}

data[0]();
data[1]();
data[2]();

// 0
// 1
// 2

最后来看看展开运算符spread(…)

除了arguments这个关键字,在新版ES6的JavaScript中另外提供了一个展开运算符(spread),它就是「...」三个点,这个...有什么用呢?

根据MDN对于展开运算符spread的描述如下:

The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.

简单来说,就是它可以把函数中许多的参数(arguments)或数组中许多的元素(elements)形成一个新的变量。

举例来说:

在函数的部分,在参数的地方我们用”...other“,other是你想要储存成的数组变量名称,可以自己取。

在执行函数的地方,原本我们只有三个参数(house, car, money),也只能填写三个参数;但使用了展开运算符”...“后,我们在执行函数的地方就可以带入不只三个参数(例如,我在最后面又加了”老婆”和”孩子”),这些多的参数值最后都会被放到other这个数组当中。

function MyFavorite(house, car, money, ...other){

console.log(other);
console.log('What my favorite are' + house + ',' + car + ',' + money + ',' + other);

}

MyFavorite("别墅", "法拉利", "一亿");
MyFavorite("别墅", "法拉利", "一亿", "老婆", "孩子");

结果就会长的像这样子:

展开运算符还有其他的使用方式,像是把数组元素做连接等等,使用灵活的话相当方便,如果有需要的话,可以参考引1。

引1:Microsoft Developer Netword:展开运算符(…)

应用

1.利用arguments实现方法的重载

下面我们利用arguments对象来实现一个参数相加的函数,不论传入多少参数都行,将传入的参数相加后返回。

function add() {
var len = arguments.length,
sum = 0;
for(;len--;){
sum += arguments[len];
}
return sum;
}

console.log( add(1,2,3) ); //6
console.log( add(1,3) ); //4
console.log( add(1,2,3,5,6) ); //17

由于JS是一种弱类型的语言,没有重载机制,当我们重写函数时,会将原来的函数直接覆盖,这里我们能利用arguments,来判断传入的实参类型与数量进行不同的操作,然后返回不同的数值。

2.利用arguments.callee实现递归

先来看看平常我们是怎么实现递归的,这是一个结算阶乘的函数。

function foo(num) { 
if(num<=1) {
return 1;
}else {
return num * foo(num-1);
}
}

但是当这个函数变成了一个匿名函数时,我们就可以利用callee来递归这个函数。

function foo(num) { 
if(num<=1) {
return 1;
}else {
return num * arguments.callee(num-1);
}
}

这个方法虽然好用,但是有一点值得注意,ECMAScript4中为了限制JS的灵活度,让JS变得严格,新增了严格模式,在严格模式中我们被禁止不使用var来直接声明一个全局变量,当然这不是重点,重点是arguments.callee这个属性也被禁止了。不过这都不是事儿,ES6为我们新增了很多好用的变量声明方式和新的语法糖,作为一个时髦的前端,我们赶紧学习一些ES6的新语法吧。

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!



JavaScript  

JavaScript arguments

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!