理解参数

200 阅读4分钟

理解参数

  • ECMAScript 函数的参数跟大多数其他语言不同。ECMAScript 函数既不关心传入的参数个数,也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你也可以传一个、三个,甚至一个也不传,解释器都不会报错。

  • 之所以会这样,主要是因为 ECMAScript 函数的参数在内部表现为一个数组,函数被调用时总会接收一个数组,但函数并不关心这个数组包含什么。如果数组中什么也没有,那没有问题;如果数组的元素超出了要求,那也没有问题。事实上,在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中获取传进来的每个参数值。

  • arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数就是 arguments[0],第二个参数是 arguments[1])。而要确定传进来多少个参数,可以访问 arguments.length 属性。

  • 在下面的例子中,sayHi()函数的第一个参数叫 name:

    function sayHi(name, message) {
      console.log(`hello${name}, ${message}`);
    }
    // 可以通过arguments[0],取得相同的参数值。因此,把函数重写成不声明函数也可以
    function sayHi() {
      console.log(`hello${arguments[0]}, ${arguments[1]}`);
    }
    
  • 再重写后的代码中,没有命名参数。name 和 message 参数都不见了,但函数照样可以调用。这就表明,ECMAScript 函数的参数只是为了方便才写出来的,并不是必须写出来的。与其他语言不同,再 ECMAScript 中的命名参数不会创建让之后的调用必须匹配的函数签名。这是因为根本不存在验证命名的参数的机制。

  • 也可以通过 arguments 对象的 length 属性检查传入的参数个数。下面的例子展示了在每调用一个函数时,都会打印传入的参数个数:

    function howManyArgs() {
      return arguments.length;
    }
    howManyArgs("string", 45); // 2
    howManyArgs(); // 0
    howMangArgs(12); // 1
    
  • 这个例子分别打印出 2、0 和 1。既然如此,那么开发者可以想传多少个参数就传多少个参数。比如:

    function doAdd() {
      if (arguments.length === 1) {
        console.log(arguments[0] + 10);
      } else if (arguments.length === 2) {
        console.log(arguments[0] + arguments[1]);
      }
    }
    
    doAdd(10); // 20
    doAdd(20, 30); // 50
    
  • 这个函数 doAdd()在只传一个参数时会加 10,在传两个参数时会将他们相加,然后返回。虽然不像真正的函数重载那么明确,但这已经足以弥补 ECMAScript 在这方面的缺失了。

  • 还有一个必须理解的重要方面,那就是 arguments 对象可以跟命名参数一起使用,比如:

    function doAdd(num1, num2) {
      if (arguments.length === 1) {
        console.log(num1 + 10);
      } else if (arguments.length === 2) {
        console.log(arguments[0] + num2);
      }
    }
    
  • 在这个 doAdd() 函数中,同时使用了命名参数和 arguments 对象。命名参数 num1 保存着与 arguments[0]一样的值,因此使用谁都无所谓。

  • arguments 对象的另一个有意思的地方就是,它的值始终会与对应的命名参数同步。来看下面的例子:

    function doAdd(num1, num2) {
      argument[1] = 10;
      console.log(arguments[0] + arguments[1]);
    }
    
  • 这个 doAdd()函数把第二个参数的值重写为 10。因为 arguments 对象的值会同步到对应的命名参数,所以修改 argumenets[1]也会修改 num2 的值,因此两者的值都是 10。但这并不意味着它们都访问同一个内存地址,它们在内存中还是分开的,只不过会保持同步而已。另外还要记住一点:如果只传了一个参数,然后把 arguments[1]设置为某个值,那么这个值并不会反映到第二个命名参数。这是因为 arguments 对象的长度是根据传入的参数个数,而非定义函数时给出的命名参数个数确定的。

箭头函数中的参数

  • 如果函数是使用箭头语法定义,那么传给函数的参数将不能使用 arguments 关键字访问,而只能通过定义的命名参数访问。
    function foo() {
      console.log(arguments[0]);
    }
    foo(5); // 5
    let bar = () => {
      console.log(arguments[0]);
    };
    bar(5); //ReferenceError: arguments is not defined
    
  • 虽然箭头函数中没有 arguments 对象,但可以在包装函数中把它提供给箭头函数:
    function foo() {
      let bar = () => {
        console.log(arguments[0]);
      };
      bar();
    }
    foo(5); // 5