正文
一、简述
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
方法
在函数外使用会抛出 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.