Arguments 对象与简易柯里化

简介: Arguments 对象与简易柯里化

正文


一、简述


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.

目录
相关文章
|
7月前
|
程序员 编译器 C++
C++中的函数重载(Function Overloading)
C++中的函数重载(Function Overloading)
84 2
Lambda 语法糖《方法引用》
Lambda 语法糖《方法引用》
function () {}()匿名函数
function () {}()匿名函数
41 0
|
编译器 程序员 C++
函数重载(function overloading)
函数重载(function overloading)是编程语言中一种支持多个同名函数的特性,这些函数在参数列表(参数类型和数量)上有所不同。当调用一个重载函数时,编译器会根据函数参数列表的具体情况进行匹配,然后调用相应的函数实现。
115 5
|
JavaScript
函数arguments讲解
函数arguments讲解
函数arguments讲解
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | 闭包 parameterTypes 和 maximumNumberOfParameters 成员用法 )
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | 闭包 parameterTypes 和 maximumNumberOfParameters 成员用法 )
120 0
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | 闭包 parameterTypes 和 maximumNumberOfParameters 成员用法 )
|
机器学习/深度学习
【Groovy】闭包 Closure ( 闭包参数绑定 | curry 函数 | rcurry 函数 | ncurry 函数 | 代码示例 )(二)
【Groovy】闭包 Closure ( 闭包参数绑定 | curry 函数 | rcurry 函数 | ncurry 函数 | 代码示例 )(二)
130 0
【Groovy】闭包 Closure ( 闭包参数绑定 | curry 函数 | rcurry 函数 | ncurry 函数 | 代码示例 )(二)
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | this、owner、delegate 成员区别 | 静态闭包变量 | 闭包中定义闭包 )(二)
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | this、owner、delegate 成员区别 | 静态闭包变量 | 闭包中定义闭包 )(二)
164 0
【Groovy】闭包 Closure ( 闭包类 Closure 简介 | this、owner、delegate 成员区别 | 静态闭包变量 | 闭包中定义闭包 )(二)
【Groovy】闭包 Closure ( 闭包作为函数参数 | 代码示例 )
【Groovy】闭包 Closure ( 闭包作为函数参数 | 代码示例 )
180 0
【Groovy】闭包 Closure ( 闭包作为函数参数 | 代码示例 )
|
索引
类数组对象与arguments
类数组对象与arguments
122 0