Post

Parameter Normalization Pattern

A pattern for handling flexible function parameters that accept both shorthand and full-form inputs.

写一个日期格式化函数,第二个参数 formatter 想支持好几种传法:

1
2
3
4
5
6
7
8
9
10
11
12
formate(new Date(), "date");              // 2023-7-10
formate(new Date(), "datetime");          // 2023-7-10 8:00:30
formate(new Date(), "date", true);        // 2023-07-10(补零)
formate(new Date(), "datetime", true);    // 2023-07-10 08:00:30(补零)
formate(new Date(), "yyyy年MM月dd日 HH:mm:ss");  // 自定义格式
formate(new Date("2023/7/10"), function(dateInfo) {  // 完全自定义逻辑
  const { year } = dateInfo;
  const thisYear = new Date().getFullYear();
  if (year < thisYear) return `${thisYear - year}年前`;
  if (year > thisYear) return `${year - thisYear}年后`;
  return "今年";
});

formatter 可以是字符串简写、自定义格式模板、或者一个函数。如果在函数内部把这几种情况全用 if/else 铺开处理,代码会很乱。

想了一下,有没有某种形式能涵盖所有情况?函数可以。 字符串简写 "date" 可以转成函数,自定义格式模板也可以转成函数,函数本身就是函数。如果先把所有传入的格式统一转成函数,再执行,后面的逻辑就不用管参数是什么形式进来的了。这就是「参数归一化」:在函数入口把各种形式收敛成一种内部表示。

转换逻辑放在独立的 _formateNormalize 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function _formateNormalize(formatter) {
  if (typeof formatter === "function") {
    return formatter;
  }
  if (typeof formatter !== "string") {
    return new TypeError("formatter must be a string or a function");
  }

  if (formatter === "date") {
    formatter = "yyyy-MM-dd";
  }
  if (formatter === "datetime") {
    formatter = "yyyy-MM-dd HH:mm:ss";
  }

  // 字符串模板转函数:把 yyyy、MM 等占位符替换成实际值
  function formatFun(dateInfo) {
    const { yyyy, MM, dd, HH, mm, ss } = dateInfo;
    return formatter
      .replaceAll("yyyy", yyyy)
      .replaceAll("MM", MM)
      .replaceAll("dd", dd)
      .replaceAll("HH", HH)
      .replaceAll("mm", mm)
      .replaceAll("ss", ss);
  }
  return formatFun;
}

主函数入口先调归一化,拿到统一的函数形式,后面只跟这一种形式打交道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function formate(date, formatter, isPad) {
  formatter = _formateNormalize(formatter);

  const dateInfo = {
    year: date.getFullYear(),
    month: date.getMonth() + 1,
    date: date.getDate(),
    hours: date.getHours(),
    minutes: date.getMinutes(),
    seconds: date.getSeconds(),
  };

  dateInfo.yyyy = dateInfo.year.toString();
  dateInfo.MM = dateInfo.month.toString();
  dateInfo.dd = dateInfo.date.toString();
  dateInfo.HH = dateInfo.hours.toString();
  dateInfo.mm = dateInfo.minutes.toString();
  dateInfo.ss = dateInfo.seconds.toString();

  function pad(prop, len) {
    dateInfo[prop] = dateInfo[prop].padStart(len, "0");
  }
  if (isPad) {
    pad("yyyy", 4);
    pad("MM", 2);
    pad("dd", 2);
    pad("HH", 2);
    pad("mm", 2);
    pad("ss", 2);
  }

  return formatter(dateInfo);
}

computed 的参数处理也是同样的思路——它可以接收函数或者对象 { get, set },入口先归一化成 getter/setter,后面只用 getter 就行,不再管外面传进来的是什么形式。

This post is licensed under CC BY 4.0 by the author.