在我们使用javascript时候,由于javascript不会对传入形参(函数声明时候定义的参数)的个数进行校验,对于不定实参的获取就需要用到我们的Arguments实参对象对于参数进行处理了
一、Arguments定义
arguments 是一个对应于传递给函数的参数的类数组对象(关于类数组对象参考掘金文章),arguments实参对象是在我们调用的函数的时候自动创建的,下面我们就通过实际案例来去看一下arguments
function f() {
console.log(arguments)
if(arguments.length === 3) {
arguments.forEach(i => console.log(i))
}
if (arguments.length === 2) {
console.log(arguments[0] + arguments[1])
}
}
f(1,2) // 输出 3
f(1,2,3) // 报错,没有forEach方法,
从上面例子我们可以得出下面结论:
- arguments有length属性,可以拿到参数个数
- arguments可以通过类数组下标的方式获取实参值
- arguments不能通过使用数组的一些方法进行遍历(forEach、map...)
因此可以看出他的结构更多的像是如下所示:
{
0: 1,
1: 1,
length: 2,
callee: xxxx,
....
}
二、arguments存在的场景
首先我们简单的执行一个例子,如下:
function a() {
console.log(arguments, 'a');
}
const b = () => {
console.log(arguments, 'b');
};
function c() {
console.log(arguments, 'c');
arguments[3] = '123'
console.log(arguments, arguments.length)
}
a(1,2,3); // Arguments(3) .....
c(1,2,3); // arguments(3) ...
// Arguments(3) [1, 2, 3, 3: '123', ...] 3
b(1,2,3); // arguments is not defined
从上面我们能总结出来一下两点:
- 普通函数是有arguments实参对象的;
- 箭头函数式没有arguments实参对象;
- arguments实参对象我们无法通过增加值的方式去改变length长度
那么我们在看一看下面一个例子
function a() {
console.log(arguments, 'a');
function b() {
console.log(arguments, 'b');
}
const c = () => {
console.log(arguments, 'c');
};
b(1,2,3);
c(1,2,3);
}
a('a', 'b', 'c');
// 结果
// Arguments(3) [ 'a', 'b', 'c' , ...]
// Arguments(3) [ 1, 2, 3, ...]
// Arguments(3) [ 'a', 'b', 'c' , ...]
这个其实我们可以理解为,箭头函数本身没有arguments 实参对象,就是去上级作用域寻找arguments,所有内部函数c里面的arguments其实和外部函数a是一致的;
如果你已经理解了上面,那么我们试着去解读一下防抖函数(debounce),具体代码如下
function debounce(fn, delay = 500) {
let timer = null;
console.log(arguments, '外层')
return function () {
if (timer) {
clearTimeout(timer);
}
console.log(arguments, '内层')
timer = setTimeout(() => {
console.log(arguments, '函数执行时')
// 执行事件的回调函数
fn.apply(this, arguments);
// 执行后清空定时器
timer = null
}, delay)
}
}
// 调用
const a = (a, b) => {
console.log(a, b, '调用')
}
const debounceA = debounce(a, 1000)
debounceA(1, 2)
// 输出结果
// [(a, b) => console.log(a,b, '调用'), 1000, ....] '外层'
// [1, 2, ...] '内层'
// [1, 2, ...] '函数执行时'
首先外层的arguments实参对象的值,我们很容易理解,无法就是在我通过const debounceA = debounce(a, 1000)
来去生成防抖函数时候传入的 a, 1000,
那么对于内层函数,和定时器内部执行的的实参,我们需要理解到debounceA其实是一个不定形参的函数,当我们通过debounceA(1, 2)
执行的时候,就等于给return出来没有具体形参的的function传入1,2作为参数,当然箭头函数没有arguments实参对象,需要去父级作用域去寻找,因此对于内层函数和定时器的arguments实参对象,两个是一致的;
三、对于arguments实参对象数据转换
arguments实参对象是类数组对象,如果我们需要对arguments进行处理,一般处理方法有三种,如下:
- 第一种
[].slice.call(arguments)
[].slice.call(arguments) => 返回参数数组
// 核心机制如下:
let args = [];
let obj = {0:'hello',1:'world',length:2};
for (let i = 0; i < obj.length; i++) {
args.push(obj[i]);
}
console.log(args); // ['hello', 'world']
简单说一下机制(前提是大家很懂call()和slice()这两个api的用法)
1、首先call(this, ...),我们传入arguments以后,就相当于 arguments.slice()这个意思(当然这个是通过call的方式临时赋能给arguments的能力,因此,你不能上来就很憨憨的写arguments.slice());
2、然后slice(start, end)如果两个参数都没有给,就会返回一个一模一样的新数组,不会影响原来的数组,这样的话我们就可以通过上面的的核心机制代码去试着理解其将arguments转化为数组的过程了;
- 第二种:通过es6数组提供的新方法from这个方法进行转化,当然这个方法还有其他的妙用,后续在说明
Array.from(arguments)
四、arguments实参对象callee属性说明
参考下面代码,我们通过代码进行解读
let f = function(x) {
if (x <= 1) {
return 1
}
return x * arguments.callee(x - 1)
}
callee其实就是指代当前正在执行的函数,上面代码arguments.callee 等价于 f,一般用于匿名函数递归调用;
当然我们也需要注意,如果我们是在严格模式下('use strict';),我们就无法通过arguments.callee去做一些操作了,如下图所示;