在 JavaScript
中,我们可以通过 Function.length
来得到函数签名中参数的个数,如果要知道实际参数的个数,在非箭头函数中,我们可以从函数内部的局部变量 arguments
对象得知。它包含了传递给函数的全部参数,是一个类数组对象,我们可以通过下标(例如 arguments[0]
)来取得其中参数,arguments.length
得到传递的参数个数。
arguments 的类型
使用常规的类型判断方法,我们可以知道 arguments
的具体类型是什么:
function h() {
console.log('typeof: ', typeof arguments);
console.log('toString: ', Object.prototype.toString.call(arguments));
}
// h()
// typeof: object
// toString: [object Arguments]
arguments 转换为数组
arguments
是一个类数组对象,它没有诸如 forEach
map
等方法,我们可以使用 Array.from
、 Array.prototype.slice
、扩展运算符等方法将其转换为一个数组来使用:
function h() {
const args1 = Array.from(arguments);
const args2 = Array.prototype.slice.call(arguments);
const args3 = [...arguments];
console.log(args1);
console.log(args2);
console.log(args3);
}
arguments 与剩余参数、默认参数、解构赋值
既然 arguments
可以通过下标来读取,那么能否通过下标去改变它的值呢?
JS 中的参数是按值传递的,如果我们改变了参数列表中某个参数的值,arguments
中对应的值会不会跟着改变?
以上的疑问总结下来就是: arguments
是不是跟传递给函数的参数动态链接的?
根据 ECMA-262 的描述,经过测试后,它们之间的具体关系是这样的:
- 在非严格模式下,
arguments
中的值,跟传给函数的参数在顺序上是一一对应的,也就是 arguments[0] 对应传给函数的第一个参数,但是如果把arguments
中的某个位置值删除然后重新定义或者将其定义为访问器 (accessor) 的话,就会破坏这种关系,但是这种破坏仅限于改变的那个值。
function test(a, b, c) {
console.log('init arguments', [...arguments]);
a = 3;
arguments[0] = 1;
console.log('changed a: ', a); // a 被改为了 1
console.log('after property changed: ', [...arguments]);
arguments[1] = 3; // b 被改为了 3
console.log('after arguments changed', [...arguments]);
}
test(0, 2, 3);
// init arguments [ 0, 2, 3 ]
// changed a: 1
// after property changed: [ 1, 2, 3 ]
// after arguments changed [ 1, 3, 3 ]
// 删除了 arguments 第一个值后,a 改变不会引起 arguments 的改变
function test(a, b, c) {
console.log('init arguments', [...arguments]);
// delete a;
delete arguments[0]; // 关系破坏了
arguments[0] = 1; // 重新定义为 1
console.log('changed a: ', a); // a 依然为 0
a = 3; // 不会再改变 arguments[0] 的值
console.log('after property changed: ', [...arguments]);
arguments[1] = 3;
console.log('after arguments changed', [...arguments]);
}
test(0, 2, 3);
// init arguments [ 0, 2, 3 ]
// changed a: 0
// after property changed: [ 1, 2, 3 ]
// after arguments changed [ 1, 3, 3 ]
- 规则 1 的前提是参数中没有包含默认参数、剩余参数或者解构赋值。如果参数中包含上面三种形式,则
arguments
不会跟踪参数的值,反之亦然。
function test1(a = 50) {
a = 20;
// 存在默认值,a 不会改变 arguments 的值。
console.log(arguments[0]);
}
test1(10); // 10
function test2(a, ...rest) {
arguments[0] = 20;
console.log('a', a); // a = 10, 不会更新 a 的值
console.log([...arguments]);
}
test2(10) // [20]
function test3(a, { b }) {
console.log('a', a);
a = 20; // 不会改变 arguments
console.log([...arguments]);
}
test3(10, {}); // [ 10, {} ]
- 严格模式下
arguments
的值只是对传递给函数的参数的拷贝,不管是否包含剩余参数、默认参数或者解构赋值,它们之间都不会有动态链接的关系:
function test(a, b, c) {
'use strict'
console.log('init arguments', [...arguments]);
arguments[0] = 1; // 重新定义为 1
console.log('remained a: ', a); // a 不变
b = 3; // 不会再改变 arguments[1] 的值
console.log('after property changed: ', [...arguments]);
console.log('after arguments changed', [...arguments]);
}
test(0, 2, 3);
// init arguments [ 0, 2, 3 ]
// remained a: 0
// after property changed: [ 1, 2, 3 ]
// after arguments changed [ 1, 2, 3 ]
- 严格模式下访问
arguments.callee
会抛异常。
总结
- 箭头函数没有 arguments ;
- arguments 是一个类数组对象,可以通过下标访问,也有多种简单的方法将其转换为一个数组;
- 非严格模式下,
arguments
会跟踪传递给函数的参数,如果参数中包含默认值、解构赋值、剩余参数的话,则不会跟踪; - 严格模式下,
arguments
只是对传递给函数参数的拷贝,参数变量和arguments
的改变不会相互影响。