← 返回题目列表

ES6 默认参数有什么特点?和手动兜底有什么区别?

高频 中等 第 10 / 28 题 更新于 2026/07/28
ES6默认参数函数参数

简化版

默认参数是在形参位置给参数兜底,只有实参是 undefined 或未传时才会使用默认值;它比 x = x || value 更准确,因为不会误伤 0、空字符串和 false

详细版

默认参数的核心规则是:缺参或传入 undefined 时生效,传入 null 不生效。

function createPage(page = 1, size = 20) {
  return { page, size };
}

它和手动兜底的区别:

  • page = page || 1 会把 0 当成无效值。
  • 默认参数只处理 undefined,语义更精确。
  • 默认参数可以引用前面的参数。
  • 使用默认参数会影响函数的 length
  • 默认参数区域有自己的作用域,不能随意引用后面的参数。

面试重点是讲清楚触发条件、求值时机、作用域和 length 变化。

完整版教学

一、默认参数解决的是“缺参”而不是“假值”

ES6 之前常见兜底写法是 x = x || defaultValue。 这个写法的问题是它把所有假值都当成缺失值,包括 0''false。 默认参数的判断更窄,只在参数值为 undefined 时触发。

function page(size = 20) {
  return size;
}

page();          // 20
page(undefined); // 20
page(null);      // null
page(0);         // 0

数字例子很好记:分页 size=0 可能有业务含义。 如果用 size = size || 200 会被改成 20,这就是语义误判。

二、默认值是在调用时求值

默认参数不是函数定义时就固定下来。 每次调用函数时,如果该参数需要默认值,默认表达式才会被执行。 因此默认值可以是函数调用,也可以依赖当前环境。

let n = 0;

function nextId(id = ++n) {
  return id;
}

console.log(nextId()); // 1
console.log(nextId()); // 2
console.log(nextId(10)); // 10

这个例子说明:第 3 次调用传了 10,默认表达式不会执行,所以 n 仍然是 2。 面试追问“默认参数什么时候计算”时,要答“调用时、且仅在需要默认值时”。

三、可以引用前面的参数,不能可靠引用后面的参数

参数初始化是从左到右进行的。 后面的默认参数可以使用前面已经初始化好的参数。 但前面的参数默认值不能引用后面的参数,因为后者还处在未初始化状态。

function area(width, height = width) {
  return width * height;
}

area(5); // 25

function bad(x = y, y = 1) {
  return x + y;
}

bad(); // ReferenceError

初始化流程可以画成:

实参进入
  -> 初始化第 1 个形参
  -> 初始化第 2 个形参
  -> 初始化第 3 个形参
  -> 进入函数体

所以默认参数不是普通变量声明的装饰,它参与了函数调用前的初始化过程。

四、默认参数会影响函数的 length

函数的 length 表示“第一个带默认值参数之前的形参数量”。 它经常被框架、工具函数或测试代码用来判断函数期望接收几个参数。 一旦从某个参数开始设置默认值,后面的参数不再计入 length

function a(x, y) {}
function b(x, y = 1) {}
function c(x = 1, y) {}

console.log(a.length); // 2
console.log(b.length); // 1
console.log(c.length); // 0
函数length原因
a(x, y)2没有默认参数
b(x, y = 1)1y 开始不计入
c(x = 1, y)0第一个参数已有默认值

这不是日常业务最常用的点,但属于高频追问。

五、默认参数区域有独立作用域

当函数参数使用默认值时,参数初始化区域会形成一个独立作用域。 默认值表达式能访问外层变量,也能访问前面的参数。 函数体内部声明的变量不能被参数默认值提前访问。

let x = 1;

function demo(y = x) {
  let x = 2;
  return y;
}

demo(); // 1

这里 y = x 访问的是外层 x,不是函数体里的 let x = 2。 原因是函数体还没有开始执行。 这类题经常和作用域、暂时性死区一起考。

默认参数的判断口径很小:只管 undefined;它的执行位置很早:进入函数体之前。

六、工程中怎么用才不容易踩坑

默认参数适合表达“未传就使用默认配置”。 如果业务需要把 null 也视为缺失,需要显式处理,或者用空值合并运算符。 不要把复杂对象默认值和外部可变状态混在一起,否则读者很难判断每次调用得到的到底是什么。

function request(url, options = {}) {
  const timeout = options.timeout ?? 3000;
  return { url, timeout };
}

上面 options = {} 解决未传对象的问题,?? 3000 解决属性缺失的问题。 两层兜底分工清晰,比把所有判断塞进一个 || 表达式更可靠。

七、常见误区与追问

  • 误区:默认参数会在传入 null 时生效。 默认参数只在值为 undefined 时生效,null 是一个明确传入的值。
  • 误区:x = x || 1function f(x = 1) 等价。 前者会覆盖 0false、空字符串,后者不会。
  • 误区:默认参数在函数定义时求值。 默认表达式在每次调用时按需求值,不需要默认值时不会执行。
  • 追问:为什么 function f(x = y, y = 1){} 调用会报错? 参数从左到右初始化,x 初始化时 y 还没有完成初始化。
  • 追问:默认参数为什么影响 length length 只统计第一个默认参数之前的形参,便于表达最少必填参数数量。
  • 追问:对象参数默认值应该怎么写? 常见写法是 options = {} 配合属性级别的 ??,避免读取 undefined.timeout 报错。

八、加强记忆

记默认参数时抓四个关键词:undefined 触发、调用时求值、从左到右初始化、影响 length。实际答题可以用 0|| 误伤的例子切入,再补作用域和 length 追问,答案会比只说“给参数默认值”扎实很多。