你真的懂函数参数吗?

90 阅读5分钟

记录学习过程,如有错误欢迎指出

今天在看《Javascript语言精髓与编程实战》时,看到周爱民老师在书中讲解的一些关于函数参数的知识,特此我结合自己的理解分享给大家

如果你熟悉某些知识点,可以跳至 非简单参数

默认参数

可以理解为函数参数的默认值,假设foo函数接收3个参数.但是我们不是必须三个参数都要传

function foo(name,age,gender='未知'){
    ....
}

foo('bezos',20)//bezos 20 未知
foo('bezos',20,男)bezos 20

相信大家现在能够理解了默认参数,默认参数可以位于参数列表的任意位置

参数个数

foo.length可以获取函数参数个数

function foo(name,age,gender='未知'){
    ....
}

foo.length //2

这里为什么是2呢?不应该是3吗?

因为js不会将默认参数算进参数个数

function foo(name,age,gender='未知',height){
    ....
}

foo.length //2

现在你应该又要问了明明只有1个默认参数,为什么length还是2呢?

因为js不会将默认参数(包括自己)之后的参数算入参数个数

注意:即便传入参数undefined,默认参数也会生效

剩余参数

剩余参数用于"一个标识符对应多个传入参数"的情况 ---摘自《Javascript语言精髓与编程实战》

function foo(name,...args){
    console.log(args);
}

foo('bezos',18,'男')//[ 18, '男' ]

作用就是收集所有没有被形参所匹配的参数(仅收集形参之后未匹配的)

剩余参数和默认参数一样也不计入参数个数

前戏结束

arguments

arguments就是一个类数组对象,它包含了该函数的所有参数并且是有序

除箭头函数外的所有函数都会自动创建arguments,函数内部可以自由访问这个arguments

既然知道arguments表示所有参数,那么arguments.length是函数参数长度也不难推出

function foo(name, age) {
  console.log(arguments);
}

foo("bezos", 18);//[Arguments] { '0': 'bezos', '1': 18 }

形参和arguments的关系

首先来看两个例子

function foo(name) {
  console.log(name);
  arguments[0] = "bob";
  console.log(name);
}

foo("bezos");
/*
    bezos
    bob
*/
function bar(age) {
  console.log(arguments[0]);
  age = 20
  console.log(arguments[0]);
}

bar(18);
/*
    18
    20
*/

现在我们知道了它们俩的关系: arguments和形参是相互绑定的,会互相影响

解构arguments对象后再用解构变量进行改变是不会出现绑定效果的,所以剩余参数自然也不会触发绑定

我们再深入探讨一下,基于上面的例子.我们知道了形成和arguments是相互绑定的,但是一旦将参数从arguments中解构出来,就不会触发绑定.由此我们知道了arguments仅是对当前全部参数引用,所以修改arguments就相当于在修改形参

最后我觉得arguments就是js给你的一个快速访问未被形参匹配的快捷路径,它并不是真正存在的一个类,而是形参组成的类

非简单参数(重点开始)

默认参数,剩余参数,模板参数 统称为"非简单参数" --- 摘自《Javascript语言精髓与编程实战》

铺垫了这么多终于可以引出下文了

js函数处理调用时的实参时有两种方式:

  • 用初始化赋值
  • 形参与arguments绑定

js还得从这两种方式中二选一

非简单参数只能使用"用初始化赋值"

一旦使用"用初始化赋值"这种方式,那么就会导致js函数增加三个限制:

  • 无法从函数内部显示开启严格模式,可以从函数外开启从而影响函数内
  • 不管是否严格模式,参数声明不再接受"重名参数"
  • 不管是否严格模式,形参和arguments之间接触绑定

上述前三个限制,最主要的还是第三个.因为形参和arguments之间解除了绑定,当在使用默认参数时就会出现一种特殊情况

function foo(name = "bezos", age, gender = "未知") {
  console.log(...arguments);
  console.log(name, age, gender);
}

foo(undefined, 18, "男");
/*
    undefined 18 男
    bezos 18 男
*/

发现了没?我们在函数调用时明明参数1传的undefined,默认参数理因生效,那么为什么输出的arguments是undefined 18 男呢?应该是1 18 男才对呀

这就是"用初始化赋值"所带来的特殊作用

我们来梳理一下:

首先调用foo(),然后函数通过"用初始化赋值"初始参数

初始器识别到参数1传入了undefined,默认参数生效name = "bezos"

但是我们使用到了默认参数,属于非简单参数,触发了上面提到的三个限制,形成和arguments之间解绑,所以name的引用arguments拿不到了,拿不到引用也就察觉不到name发生了变化,还以为name是传入的undefined

最后输出undefined 18 男

非惰性求值(小知识)

思考以下输出

function foo(value) {
  console.log(value);
}

let i = 0;
foo((i += 10), (i += 10));
foo(i);

最终输出为

10
20

foo接收一个参数,虽然后面的参数没有被接收,但是被执行了,以致于第二foo输出20

这就体现了非惰性求值

所以我们在实际开发中应匹配参数个数完美,避免不必要的非惰性求值,导致浪费