先记住这个答案
函数的 length 表示它声明了几个「必须传」的形参,是在定义时就确定的,和调用时传了几个参数无关。计数规则是从左往右数,数到第一个带默认值的参数就停下,它和它后面的参数都不算。剩余参数 ...args 也不算。解构参数不管里面有几个字段,整体只算一个。
length是声明的形参数,和实际传参无关- 从第一个默认参数开始,后面的都不计
- 剩余参数不计,解构参数算一个
bind预置几个参数,length就减几,最小为 0
逐个验证计数规则
把默认参数、剩余参数和解构放在不同位置,直接打印 length。
lengthJavaScriptfunction a(x, y, z) {
return arguments.length // 实际传入的参数个数
}
function b(x, y = 1, z) {} // y 有默认值,从 y 开始都不算
function c(x, ...rest) {} // rest 不算
function d({ id, name }, [first]) {} // 两个解构参数,各算一个
const e = (...args) => {}
console.log(a.length, a(1, 2, 3, 4))
console.log(b.length)
console.log(c.length)
console.log(d.length)
console.log(e.length)查看输出与解释
3 4
1
1
2
0a 声明了三个形参,length 就是 3,调用时传了四个,arguments.length 是 4,两者没关系。b 里的 z 虽然没写默认值,但它在 y 后面,也不算,所以只有 x 一个。c 只数 x。d 有两个解构参数,算 2。e 只有剩余参数,是 0。
为什么要这么设计?可以把 length 理解成「调用方至少应该传几个参数」。有默认值的参数本来就是可选的,而一旦中间出现了可选参数,后面的参数在位置上也就没法强制要求了,所以干脆停止计数。
bind 和包装函数会改掉 length
bind 返回的新函数,length 等于原函数的 length 减去预置参数的个数,最小是 0。比如 function f(a, b, c) {},f.bind(null, 1).length 是 2,f.bind(null, 1, 2, 3, 4).length 是 0。只绑定 this、不预置参数时,length 不变。
更常见的坑是自己写的包装函数。节流、防抖、日志装饰器这类工具,内部通常返回 (...args) => fn(...args),包装后的函数 length 变成了 0,原来的信息丢了。
- 柯里化为什么会被
length坑 - 很多
curry实现用fn.length判断参数凑够了没有。原函数一旦加了默认参数,比如(a, b, c = 0) => a + b + c,length从 3 变成 2,传完a、b就提前执行了,c再也传不进去。被防抖包装过的函数length是 0,curry会在第一次调用时就直接执行。所以靠谱的curry都允许手动传入参数个数,不完全依赖length。
容易答错的地方
- 以为
length是所有形参的个数 - 默认参数会截断计数,剩余参数也不算。
function f(a = 1, b) {}的length是 0,不是 1 也不是 2。 - 以为
length会随调用变化 length是函数对象上的属性,定义时就定好了。想知道这次传了几个参数,要在函数内部看arguments.length或者剩余参数数组的长度。- 以为可以直接给
length赋值 length是只读的,fn.length = 5不会生效,严格模式下还会抛TypeError。它是可配置的,真要改只能用Object.defineProperty,但业务代码里基本没有理由这么做。
面试官还会怎么问?
arguments.length 和 fn.length 有什么区别?
fn.length 是函数声明的形参数,在函数外面就能读,永远不变。arguments.length 是这一次调用实际传入的参数个数,只能在函数内部读,每次调用都可能不一样。
Express 怎么通过参数个数区分错误处理中间件?
Express 会检查中间件函数的 length,是 4 的就当作错误处理中间件,签名是 (err, req, res, next)。所以错误处理中间件即使用不到 next,也必须把四个参数都写上,写成三个它就会被当成普通中间件。
TypeScript 里的可选参数会影响 length 吗?
function f(a: number, b?: number) 编译成 JavaScript 后就是 function f(a, b),问号只存在于类型层面,length 是 2。真正写了默认值 b = 0 时,结果还要看编译产物:保留默认参数语法的编译目标下 length 是 1;如果编译到 ES5,默认值会被改写成函数体里的判断,形参列表还是 (a, b),length 又变回 2。拿不准时直接在构建产物里打印一下。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。