JS 默认参数:为函数参数设置默认值,简化函数签名

各位好,欢迎来到今天的“JS默认参数:偷懒的艺术”讲座。今天咱们聊聊JavaScript中一个非常实用,但经常被忽视的特性——默认参数。掌握它,能让你写出更简洁、更可读的代码,从此告别冗余的参数检查,走上偷懒(啊不,是高效)的康庄大道。

一、故事的开始:没有默认参数的苦日子

在ES6(ECMAScript 2015)之前,JavaScript并没有直接的默认参数语法。这意味着,如果你想给函数参数设置默认值,你得手动检查参数是否传入,然后才能赋默认值。

先看个例子,一个简单的打招呼函数:

function greet(name) {
  name = name || 'World'; // 传统的默认值处理方式
  console.log(`Hello, ${name}!`);
}

greet('Alice'); // 输出: Hello, Alice!
greet();      // 输出: Hello, World!
greet(null);   // 输出: Hello, World! (注意这里的陷阱)
greet(undefined); // 输出: Hello, World!

在这个例子中,我们使用 || 操作符来检查 name 是否有值。如果没有值(或者值为假值,比如 nullundefined0""),就将 name 设置为 'World'

这种方式在很多情况下都能工作,但存在一些问题:

  • 可读性差: 逻辑比较分散,不够清晰。
  • 容易出错: || 操作符会把所有假值都当成“没有传入参数”,这可能会导致一些意想不到的bug。比如,如果用户真的想传入一个空字符串 "" 作为名字,结果会被强制改成 'World'
greet(""); // 输出: Hello, World!  (意料之外的行为)

所以,这种方式虽然能实现默认参数的效果,但并不完美。

二、ES6的救星:默认参数语法

ES6引入了默认参数语法,让我们可以直接在函数声明中为参数指定默认值,就像这样:

function greet(name = 'World') {
  console.log(`Hello, ${name}!`);
}

greet('Alice'); // 输出: Hello, Alice!
greet();      // 输出: Hello, World!
greet(null);   // 输出: Hello, null!  (注意这里的变化)
greet(undefined); // 输出: Hello, World!
greet("");       // 输出: Hello, !  (空字符串被正确处理)

看到了吗?代码更简洁了,而且行为也更符合预期了。

默认参数的特点:

  • 简洁明了: 直接在参数列表中指定默认值,代码更易读。
  • 只在 undefined 时生效: 默认值只会在参数为 undefined 时才会生效。如果传入 null,参数的值就是 null,不会被默认值覆盖。
  • 从左到右求值: 默认参数表达式从左到右求值,后面的参数可以使用前面参数的值。
  • 可以使用表达式: 默认值可以是任何有效的 JavaScript 表达式,包括函数调用。

三、默认参数的进阶用法

  1. 使用函数作为默认值

默认值可以是函数调用,这使得我们可以根据不同的情况动态计算默认值。

function getDefaultName() {
  console.log("Calculating default name...");
  return 'Guest';
}

function greet(name = getDefaultName()) {
  console.log(`Hello, ${name}!`);
}

greet('Bob');  // 输出: Hello, Bob! (getDefaultName() 不会被调用)
greet();      // 输出: Calculating default name...
              //       Hello, Guest! (getDefaultName() 会被调用)

在这个例子中,getDefaultName() 函数只会在 name 参数为 undefined 时才会被调用。

  1. 使用前面的参数作为默认值

默认参数可以依赖于前面的参数。

function createPoint(x = 0, y = x) {
  console.log(`Point: (${x}, ${y})`);
}

createPoint(5);   // 输出: Point: (5, 5)
createPoint(2, 7); // 输出: Point: (2, 7)
createPoint();    // 输出: Point: (0, 0)

在这个例子中,y 的默认值是 x。如果只传入 xy 就会使用 x 的值作为默认值。

注意: 参数的顺序很重要!后面的参数才能使用前面参数的默认值。如果你想让 x 的默认值依赖于 y,是不行的(至少不能直接用默认参数语法实现)。

  1. 解构赋值与默认参数

默认参数可以和解构赋值结合使用,处理对象或数组参数的默认值。

function processOptions({ width = 100, height = 200, color = 'black' } = {}) {
  console.log(`Width: ${width}, Height: ${height}, Color: ${color}`);
}

processOptions({ width: 300, color: 'red' }); // 输出: Width: 300, Height: 200, Color: red
processOptions({ height: 150 });              // 输出: Width: 100, Height: 150, Color: black
processOptions();                                // 输出: Width: 100, Height: 200, Color: black
processOptions({});                               // 输出: Width: 100, Height: 200, Color: black

function processArray([first = 1, second = 2, third = 3] = []) {
    console.log(`First: ${first}, Second: ${second}, Third: ${third}`);
}

processArray([4,5]); // First: 4, Second: 5, Third: 3
processArray([]); // First: 1, Second: 2, Third: 3
processArray(); // First: 1, Second: 2, Third: 3

在这个例子中,processOptions 函数接受一个对象作为参数。我们使用解构赋值来提取对象的属性,并为每个属性设置默认值。 注意 = {} 的使用,这确保了即使没有传入任何参数,函数也能正常工作,而不会因为尝试解构 undefined 而报错。

四、默认参数的陷阱与注意事项

  1. undefinednull 的区别

再次强调,默认值只会在参数为 undefined 时生效。如果传入 null,参数的值就是 null

function logValue(value = 'Default Value') {
  console.log(value);
}

logValue(undefined); // 输出: Default Value
logValue(null);      // 输出: null
  1. 默认参数表达式的执行时机

默认参数表达式只会在参数为 undefined 时才会被执行。

let counter = 0;

function increment(num = ++counter) {
  console.log(`Num: ${num}, Counter: ${counter}`);
}

increment(); // 输出: Num: 1, Counter: 1
increment(); // 输出: Num: 2, Counter: 2
increment(10); // 输出: Num: 10, Counter: 2 (counter 没有被改变)
  1. 严格模式下的参数重名

在严格模式下,如果函数参数列表中有重名的参数,或者参数名与函数体内的变量名相同,都会导致语法错误。 默认参数也不能和参数列表中其他参数重名。

"use strict";
// 报错: SyntaxError: Duplicate parameter name not allowed in this context
// function foo(a, a, b = 1) {
//   console.log(a, a, b);
// }

// 报错:SyntaxError: Identifier 'a' has already been declared
// function bar(a, b = a) {
//     "use strict";
//     var a;
//     console.log(a, b);
// }
  1. 性能考量

虽然默认参数很方便,但在某些性能敏感的场景下,过度使用可能会带来一些开销。 每次调用函数时,JavaScript引擎都需要检查参数是否为 undefined,并根据需要执行默认参数表达式。 对于简单的默认值(比如数字或字符串),这种开销通常可以忽略不计。但如果默认值是复杂的表达式或函数调用,就需要考虑其性能影响。

五、实战演练:几个小例子

  1. 创建URL
function createURL(baseURL, path = '/', queryParams = {}) {
  let url = baseURL + path;
  const query = Object.entries(queryParams)
    .map(([key, value]) => `${key}=${value}`)
    .join('&');

  if (query) {
    url += '?' + query;
  }

  return url;
}

const myURL = createURL('https://example.com', '/articles', { page: 2, per_page: 10 });
console.log(myURL); // 输出: https://example.com/articles?page=2&per_page=10

const homeURL = createURL('https://example.com');
console.log(homeURL); // 输出: https://example.com/

const aboutURL = createURL('https://example.com', '/about');
console.log(aboutURL); // 输出: https://example.com/about
  1. 格式化日期
function formatDate(date = new Date(), format = 'YYYY-MM-DD') {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');

  return format
    .replace('YYYY', year)
    .replace('MM', month)
    .replace('DD', day);
}

console.log(formatDate()); // 输出: 当前日期,格式为 YYYY-MM-DD
console.log(formatDate(new Date(), 'MM/DD/YYYY')); // 输出: 当前日期,格式为 MM/DD/YYYY
  1. 计算价格
function calculatePrice(price, discount = 0, taxRate = 0.05) {
  const discountedPrice = price * (1 - discount);
  const tax = discountedPrice * taxRate;
  return discountedPrice + tax;
}

console.log(calculatePrice(100)); // 输出: 105 (5% 的税)
console.log(calculatePrice(200, 0.1)); // 输出: 189 (10% 的折扣,5% 的税)

六、总结:默认参数的价值

默认参数是JavaScript中一个非常实用的特性,它可以:

  • 简化函数签名,使代码更易读。
  • 减少冗余的参数检查代码,提高开发效率。
  • 使函数更健壮,能处理更多的情况。

但是,在使用默认参数时,也要注意一些陷阱和注意事项,避免出现意想不到的bug。

总而言之,默认参数是JavaScript工具箱中一个强大的武器,学会灵活运用它,能让你写出更优雅、更高效的代码。

最后,用一张表格总结一下默认参数的关键点:

特性 描述 示例
语法 在函数参数列表中使用 = 符号指定默认值。 function greet(name = 'World') { ... }
生效条件 默认值只在参数为 undefined 时生效。 greet(undefined); // 使用默认值
表达式 默认值可以是任何有效的 JavaScript 表达式。 function greet(name = getDefaultName()) { ... }
参数依赖 后面的参数可以使用前面参数的值作为默认值。 function createPoint(x = 0, y = x) { ... }
解构赋值 可以和解构赋值结合使用,处理对象或数组参数的默认值。 function processOptions({ width = 100, height = 200 } = {}) { ... }
null 处理 传入 null 时,参数的值就是 null,不会被默认值覆盖。 logValue(null); // 输出 null
严格模式 严格模式下,不能有重名的参数,也不能和函数体内的变量重名。 / /
性能 对于复杂的默认参数表达式,需要考虑其性能影响。 / /

希望今天的讲座对大家有所帮助,让大家在编码的道路上更加轻松愉快。 感谢大家的聆听!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注