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.

目录
相关文章
|
5月前
|
Python
魔术方法 __call__
【6月更文挑战第28天】
37 0
|
6月前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
56 2
|
6月前
|
Java 编译器 Linux
【C++11(二)】lambda表达式以及function包装器
【C++11(二)】lambda表达式以及function包装器
|
6月前
|
存储 JavaScript 前端开发
对象字面量和对象的封装(结合柯里化)
对象字面量和对象的封装(结合柯里化)
50 0
function () {}()匿名函数
function () {}()匿名函数
38 0
|
JavaScript 前端开发
|
JavaScript
函数arguments讲解
函数arguments讲解
函数arguments讲解
|
索引
类数组对象与arguments
类数组对象与arguments
118 0
|
存储 JavaScript 前端开发
数组的高阶函数Array.prototype.reduce
数组的高阶函数Array.prototype.reduce
136 0
|
算法
arguments.callee
在函数内部,有两个特殊的对象:arguments 和 this。其中, arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
1016 0