从Function.length 与 Argument.length 区别谈到如何传递任意个数参数

之前电面有问到:“你知道一个函数的length是什么吗?”

因为没看过,也没碰到过使用场景,我没答出来。后来查了下发现是指函数的参数个数,于是也就作罢了。不过今天恰巧碰到了使用场景,且发现之前的理解也有误,于是就写一篇短文分享一下。

今天在尝试 render props 的各种扩展玩法,写了个简单的表格数据 CRUD 操作 Demo:CodeSandbox

写Demo一般从所有操作都同步开始,最后再全改成异步的情况。所以需要写一个将所有同步函数(数据加载以及增删改)都转换成异步函数的工具函数。我一开始写得如下:

const delay = ms => new Promise(_ => setTimeout(_, ms));
// 打算从今开始尽量使用 async/await
const withRequest = func => async args => {
  await delay(1000);
  if (Math.random() > 0.3) {
    func(...args);
  } else {
    message.info("操作失败!");
  }
};
// 等一秒之后 30% 失败,70% 执行操作
复制代码

这是个所谓的 Curried(库里?柯里?咖喱?)函数,用于批量改造函数的函数,接受func为参数,返回改造好的func。明眼人应该已经发现错在哪里了,不过我没有,于是走了一堆弯路,却收获不少。

用此函数包裹了我的一堆测试方法:

  add = (a,b) => a + b
  square = a => a * a
  loadData = () => this.setState({ ... })
  
  loadData = withRequest(this.loadData);
  add = withRequest(this.add);
  square = withRequest(this.square);
复制代码

立马报错跪了,于是我知道在没有参数的 loadData 函数那里跪了,并开始了我的求知之旅。

问题:

如何将任意个参数从上级函数传递给下级函数?

笨办法解决:分情况讨论

分情况讨论的关键是:如何知道函数有几个参数呢?毫无疑问我想到了fn.length, 于是写下:

const len = func.length
if(len === 0){
  func()
} else if(len === 1) {
  func(args)
} else {
  func(...args)
}
复制代码

这对了吗?答案是不对。 fn.length的定义是:函数的形参个数。也就是函数定义时的参数个数,而不是函数实际接受的参数个数。比如

const add = (a,b) => a + b
add(1,2,3,4,5)  // 3
add.length  // 2
复制代码

而问题的情况,我们需要判断的是函数接受的参数个数。这时候有一个方便的内置变量:arguments

function func1(a, b, c) {
  console.log(arguments[0]); // 1
  console.log(arguments[1]); // 2
  console.log(arguments[2]); // 3
}

func1(1, 2, 3);
复制代码

arguments 即为函数接收到的所有参数组成的(类)数组。那么用 arguments.length 替换所有 func.length 是否就对了呢?还是不对,arguments 有它的局限性:

const func1 = (a, b, c) => {
  console.log(arguments[0]); // 1
  console.log(arguments[1]); // 2
  console.log(arguments[2]); // 3
}

func1(1, 2, 3);
// error: arguments is not defined
复制代码

箭头函数没有arguments。 同时注意到现在前端代码的箭头函数会经过 babel 转译,产生的结果是 arguments 虽然不会 undefined,但会有各种怪异赋值。总之在箭头函数里别使用。

真正的解决办法

剩余参数 (Rest parameters)

const withRequest = func => async (...args) => {
  await delay(1000);
  if (Math.random() > 0.3) {
    func(...args);
  } else {
    message.info("操作失败!");
  }
};
复制代码

这段代码里出现了两个 ...args, 前者是剩余参数,后者是数组展开。两者一个是收束,一个是展开。功能相反。

function fun1(...args) {
  console.log(args.length);
}
 
fun1();  // 0
fun1(5); // 1
fun1(5, 6, 7); // 2
复制代码

剩余参数语法将“剩余”的参数收束到一个数组中, 注意和 arguments 不一样, 剩余参数是一个真正的数组。绕了一大圈,其实是忘记写了三个点。不过也算真正理解了:

  1. Function.length
  2. arguments
  3. rest & spread

最后,让我们用剩余参数挑战一个实用函数吧:

问题

写一个callAll函数,它接收任意数量的函数和任意数量的参数,如果作为参数的函数存在就用所有的参数调用那个函数。

const add = (a,b) => {console.log(a + b)}
const minus = (a,b) => {console.log(a - b)}
callAll(add, minus)(2,1) 
// 3
// 1
复制代码

答案如下:

// 剩余参数是一个真正的数组,可以使用任何数组方法
const callAll = (...fns) => (...args) => fns.forEach( fn => fn && fn(...args))
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值