持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
定义
所有函数(箭头函数外)中都有 arguments,就像 this 一样,可以直接在函数中打印查看:
// 例 1
function fn() {
const age = 18
console.log(arguments)
}
fn('Jay', 'Chou')
打印结果如下图:
只不过 this 是在函数调用执行时,在执行上下文栈(ECStack)中创建的函数执行上下文(FEC)里绑定的,而 arguments 是在 FEC 关联的 VO(Variable Object),也就是 AO(Activation Objec) 里的属性,以例 1 代码为例,内存示意图如下:
可以通过它找到传给函数的所有的参数,即使不定义形参也可以通过 arguments 使用实参。所以在 MDN 中,对 arguments 的定义是一个对应于传递给函数的参数的类数组(array-like)对象。所谓类数组,就是它有一些类似于数组的操作,下面举 3 个比较常见的例子:
- 可以通过
arguments.length
获取参数的个数; - 可以根据索引值获取指定的参数;
- 可以通过
arguments.callee
获取当前 arguments 所在的函数。
// 例 2
function fn() {
console.log(arguments.length)
console.log(arguments[1])
console.log(arguments.callee)
}
fn('Jay', 'Chou')
打印结果如下图:
但是 arguments 并不是数组,所以没有数组的一些诸如 forEach 等方法。如果想使用数组的方法,可以通过下面介绍的几种方法,将 arguments 转成数组。
转成数组
- 因为 arguments 有 length 属性,并且可以通过索引值获取,所以我们可以直接用一个 for 循环,遍历 arguments 里的每一项,并加入到一个空数组中:
// 例 3
function fn() {
const arr = []
for (let index = 0; index < arguments.length; index++) {
arr.push(arguments[index])
}
}
fn('Jay', 'Chou')
- 可以借用数组的 slice 方法:
// 例 4
function fn() {
const arr = Array.prototype.slice.call(arguments)
console.log(arr) // ['Jay', 'Chou']
}
fn('Jay', 'Chou')
Array.prototype.slice
是为了在没有数组实例的情况下能够调用到 slice 方法,所以直接[].slice.call(arguments)
用一个空数组替换 Array.prototype 实现的效果是一样的;.call(arguments)
的目的在于将 slice 方法内部的 this 指向 arguments。slice 的实现可以简单的认为是下面这样的:
// 例 5
Array.prototype.mySlice = function (begin = 0, end = this.length) {
const arr = []
for (let index = begin; index < end; index++) {
arr.push(this[index])
}
return arr
}
.call(arguments)
就是让 mySlice 方法内的 this 变为 arguments,相当于是上面的第 1 个方法了(例 3)。
- 直接使用 Array 的静态方法 from:
function fn() {
const arr = Array.from(arguments)
console.log(arr) // ['Jay', 'Chou']
}
fn('Jay', 'Chou')
Array.from()
方法可以将一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。所谓类似数组,就是拥有 length 属性和若干索引属性的对象,arguments 满足条件,所以它是一个类数组;所谓可迭代对象,就是在该对象或该对象的原型对象上有一个 Symbol.iterator 属性,用于实现 @@iterator 方法,图 1 中我们可以看到,打印 arguments 是能看到 Symbol.iterator 属性的,所以它又是一个可迭代对象。
- 可以用展开语法(Spread syntax)
因为 arguments 是可迭代对象,所以可以使用展开语法,直接构造一个字面量数组:
function fn() {
const arr = [...arguments]
console.log(arr) // ['Jay', 'Chou']
}
fn('Jay', 'Chou')
箭头函数
在箭头函数中,是没有 arguments 属性的。如果在箭头函数中打印 arguments,则与寻找一般的变量类似,会去箭头函数的父级作用域中寻找,直至全局作用域。注意,父级作用域是在函数定义时就决定了,而不是调用时。
- 在浏览器环境下
箭头函数的上层作用域,也就是全局作用域中没有 arguments 属性,打印会报错,如下:
const fn = () => {
console.log(arguments) // Uncaught ReferenceError: arguments is not defined
}
fn('Jay', 'Chou')
- 在 node 环境下
有打印出内容 ,因为全局作用域中存在 arguments:
在箭头函数中,如果想要获取没有对应形参的实参,可以使用剩余参数(Rest parameters),它的写法和展开语法一样,但可以看成是相反的作用(展开语法也可以用在函数中,只不过是在调用时):
const fn = (firstName, lastName, ...reset) => {
console.log(reset) // [18]
}
fn('Jay', 'Chou', 18)
剩余参数得到的是一个真正的数组。