JavaScript认识之——arguments

125 阅读3分钟

arguments是一个类数组结构,除了具有length属性外,它没有数组的任何方法。
所有的函数都拥有arguments对象,它是一个内置的局部变量,表示的是函数接收到的参数,也就是实参的集合。

  • 特性1:函数外无法访问
    arguments对象只存在于函数的局部作用域内,换句话说,只要在当前函数外就无法访问当前arguments对象。
    function example () {
        console.log(arguments.length) // 2
        function inner () {
            //内部函数只能访问当前作用域内的arguments,不会向外寻找上层的arguments
            console.log(arguments.length) // 0
        }
        inner();
    }
    
    example(1,2);
    console.log(typeof arguments); // undefined
  • 特性2:可以通过索引访问
    很好理解,直接看例子
    function example (name, age) {
        console.log(arguments[0]) // "罗本"
        console.log(arguments[1]) // 38
    }
    
    example("罗本", 38);
  • 特性3:由实参决定
    arguments对象的值由实参决定,和形参无关。关于arguments的length属性,在函数调用时就已经确定且不会随着函数的处理而发生改变。
    function example (name, age, hairstyle) {
        console.log(arguments.length) // 2
        
        arguments[0] = "拉姆";
        console.log(name); // "拉姆"
        age = 39;
        console.log(arguments[1]) // 39
        //传递了实参的形参,arguments和形参之间可以互相修改
        
        arguments[2] = "short";
        console.log(hairstyle); //undefined
        hairstyle = "bald";
        console.log(arguments[2]) // "short"
        //未传递实参的形参,arguments和形参之间不可以互相修改,修改其中一方不影响另一方
        
        console.log(arguments.length) // 2  虽然此时arguments[2]已经有值,但并不影响length,因为length是在函数调用时就已经确定且不会发生改变的。
    }
    
    example("罗本", 38);
  • arguments.callee
    arguments对象的属性callee,表示的是当前正在执行的函数,采用的是严格模式的比较(===)。而callee可以直接再次调用原函数,从而形成递归,但要注意的是使用了arguments.callee后函数内部的this指向也会随之发生改变。
    function example () {
        console.log(arguments.callee === example) // true
    }
    example();
    
    let sillyFunction (recursed) {
        if (!recursed) {
            console.log(this); // Window{}
            return arguments.callee(true);
        }
        console.log(this); // Arguments{}
    }
    sillyFunction();

接下来我们看看arguments在应用层面的使用

  • 实参的个数判断
    当我们规范代码,对参数的个数进行严格限制时,那么arguments就派上用场了。
    function example (name, age, hairstyle) {
        if (arguments.length !== 3) {
            throw new Error(`期望获得三个参数,实际获得参数为${arguments.length}个`);
        }
        //...
    }
    
    example("罗本", 38); //Uncaught Error: 期望获得三个参数,实际获得参数为2个
  • 任意个数参数的处理
    当一个函数传入参数不固定时,对于从第n个参数后的所有参数进行一个特殊处理的情况下,arguments可以做的非常好,这也是arguments的主要应用场景。
    function example (seperator) {
        let strArr = Array.prototype.slice().call(arguments, 1); 
        //arguments是类数组结构,不能直接调用slice,所以用call修改this指向间接调用Array的方法slice
        return strArr.join(seperator);
    }
    
    example("-", "Football", "Club", "Bayern"); "Football-Club-Bayern"
  • 模拟函数重载
    在JavaScript中是没有函数重载的,原因有3:
    • 因为JavaScript是弱类型语言,变量只有在使用的时候才确定其的类型。因此,通过形参是无法确定数据类型的。
    • 无法通过函数的参数个数来指定调用不同的函数,函数的参数个数是在函数调用时才确定下来的。
    • 使用函数声明定义的函数,如果名称相同,后者会覆盖前
    function example (num1, num2) {
        return num1 + num2;
    }
    
    function example (num1, num2, num3) {
        return num1 + num2 + num3;
    }
    
    example(1, 2); // NaN
    example(1, 2, 3); // 6
    

那么此时如果我们想不论传入多少实参,都能返回正确的处理结果应该怎么做呢?

    function example () {
        let arr = Array.prototype.slice.call(arguments);
        return arr.reduce((pre, cur) => {
            return pre + cur;
        }, 0);
    }
    
    example(1, 2); // 3
    example(1, 2, 3); // 6
    

这样不论我们传入多少参数,都能获得正确的返回值了。