美文网首页细品 JavaScript
Arguments 对象与简易柯里化

Arguments 对象与简易柯里化

作者: 越前君 | 来源:发表于2021-08-26 03:45 被阅读0次
    配图源自 Freepik

    一、简述

    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.

    相关文章

      网友评论

        本文标题:Arguments 对象与简易柯里化

        本文链接:https://www.haomeiwen.com/subject/snlyiltx.html