一、简述
arguments
是函数实参对象,常被称为“类数组”(array-like)。形如:
arguments = {
0: xx,
1: xx,
...,
n - 1: xx,
length: n // n 取决于实参的数量
}
arguments
的一些特性:
function foo() {
// 1. arguments 不是数组,自然也不具备数组 forEach 等方法
Object.prototype.toString.call(arguments) // "[object Arguments]"
arguments instanceof Array // false
// 2. arguments 对象具有数字索引属性
arguments[0] // "a"
arguments[1] // "b"
// 3. arguments 对象有 length 属性,反映实参个数。
// 但与函数的 length 属性不同,foo.length 反映形参个数。可在 ES6 之后由于参数默认值、REST 参数等新特性,使 foo.length 变得不可靠
arguments.length // 2
foo.length // 0
}
foo('a', 'b')
类数组对象的特征:含有 length
属性、索引元素属性,但不包含数组任何方法。
常见的类数组,除了 arguments
之外,还有 HTMLCollection
(通过 getElementsByName()
等返回的 DOM 列表)、NodeList
(通过 querySelectorAll()
返回的节点列表)。
二、arguments 使用
arguments
通常用于不能确定实参个数的应用场景,例如函数柯里化等。
它还经常被转换为数组使用。
// ES5
function foo() {
// 方法一:会阻止某些 JS 引擎的优化,如 V8
// 请看:https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
var args = Array.prototype.slice.call(arguments)
// var args = [].slice.call(arguments)
// 方法二(推荐,尽管丑了点)
var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments)
}
// ES6
function foo() {
// 利用 arguments 的 Iterator 接口,可以快速转化为数组
const args = [...arguments]
// const args = Array.from(arguments)
}
function foo(...args) {
// 直接使用 REST 参数,args 天生就是数组,可以直接使用 Array 的方法
}
像 Function.prototype.apply()
、Array.prototype.slice()
等方法也可接受类数组,无需转换为数组再进行操作。举个例子:
// 求最大数
function getMax() {
return Math.max.apply(null, arguments)
}
getMax(1, 2, 3) // 3
都 2021 年了,更被推荐按 ES6 的写法。
三、注意点
关于 arguments
只能在所有(非箭头)函数内部可用的局部变量。
存在于所有(非箭头)函数内部。函数上下文的
AO
对象就包括arguments
属性。箭头函数不存在
arguments
对象。有时候箭头函数内可以使用arguments
是“视觉”认知错误。ES6 的箭头函数中,要获取实参对象,可通过 REST 参数获得。
非严格模式下,
arguments
允许重新赋值。而且形参的更新,也会伴随着arguments
对象相应属性值的更新。严格模式下,则不允许对
arguments
对象赋值,且不会最终参数的变化。也不允许使用arguments.callee
方法(关于严格模式对 arguments 对象的限制,可看这篇文章)。
在函数外使用会抛出 ReferenceError
。此时它就是一个标识符而已。
// 1. 相当于一个变量 arguments,因此会抛出引用错误
console.log(arguments) // ReferenceError: arguments is not defined
// 2. 非严格模式下,可将其作为变量
let arguments = 1 // or arguments = 'any'
console.log(arguments) // 1
// 3. 严格模式下,还是将其声明变量或对其进行赋值操作
'use strict'
arguments = 1 // Wrong, SyntaxError: 'arguments' can't be defined or assigned to in strict mode code
在箭头函数内,没有 arguments
变量。例如以下这样使用同样会报错。
const foo = () => {
console.log(arguments) // ReferenceError: arguments is not defined
}
foo()
但这样用不会报错,这就是前面提到的“视觉认知”错误。
function foo() {
const bar = () => {
console.log(arguments)
// 由于箭头函数 bar 没有 arguments,
// 这里引用的 arguments 对象其实是函数 foo 的实参对象。
}
bar('b')
}
foo('a') // { 0: 'a', length: 1 }
非严格模式与严格模式,对 arguments
对象的操作。
function foo(x) {
x = 10
console.log(x) // 10
console.log(arguments[0]) // 10
}
function bar(x) {
'use strict'
x = 10
console.log(x) // 10
console.log(arguments[0]) // 1
// 以下这样将会直接抛出语法错误:SyntaxError: Unexpected eval or arguments in strict mode
// arguments = {}
}
foo(1)
bar(1)
四、求和函数(柯里化)
假设我们有一个求和函数 sum()
,要实现下面的需求:
sum(1, 2, 3) // 6
sum(1, 2)(3)(4) // 10
sum(1)(2, 3)(4, 5, 6) // 21
// ...
其实上面的需求是有问题的,它没有出口,导致不知道什么时候求和。我们可以稍微改下需求:
sum(1, 2, 3).value() // 6
sum(1, 2)(3)(4).value() // 10
sum(1)(2, 3)(4, 5, 6).value() // 21
// ...
就是说 sum()
函数的结束时机(出口)是调用 value()
方法的时候。
function sum(...args) {
const arr = [...args]
function repeat(...nextArgs) {
;[].push.apply(arr, nextArgs)
return repeat
}
repeat.value = () => {
if (!arr.length) return 0
return arr.reduce((a, b) => a + b)
}
return repeat
}
也可以改成调用 sum(1, 2)(3)(4)()
时进行求和,我们修改一下:
function sum(...args) {
if (!args.length) return 0
const arr = [...args]
function repeat(...nextArgs) {
if (!nextArgs.length) {
return arr.reduce((a, b) => a + b)
}
;[].push.apply(arr, nextArgs)
return repeat
}
return repeat
}
sum() // 0
sum(1, 2, 3)() // 6
sum(1, 2)(3)(4)() // 10
sum(1)(2, 3)(4, 5, 6)() // 21
The end.
网友评论