javascript:: 기본매개 변수 || ?? 용법으로 default option 값 설정하기


JavaScript에서 기본값을 설정할 때 자주 사용하는 방식은 다음 세 가지입니다.

value = value || "default";
value = value ?? "default";

function hello(name = "User") {
  console.log(name);
}

겉보기에는 비슷해 보이지만, 실제 동작 방식은 다릅니다. 특히 "", 0, false, null, undefined 같은 값을 다룰 때 결과가 달라지므로 주의해야 합니다.

1. || 연산자로 기본값 설정하기

가장 전통적으로 많이 사용된 방식입니다.

let userName = inputUserName || "Guest";

console.log(userName);

inputUserName이 비어 있거나 사용할 수 없는 값이면 "Guest"를 사용합니다.

다만 여기서 중요한 점은 ||null이나 undefined만 확인하는 것이 아니라, falsy 값 전체를 기준으로 동작한다는 점입니다. JavaScript의 || 연산자는 불리언 값뿐 아니라 실제 피연산자 값 중 하나를 반환할 수 있습니다. (MDN Web Docs)

2. ||에서 기본값으로 대체되는 값

다음 값들은 JavaScript에서 falsy 값으로 평가됩니다.

false
0
""
null
undefined
NaN

따라서 아래 코드는 모두 기본값을 사용합니다.

console.log("" || "Guest");        // "Guest"
console.log(0 || 100);             // 100
console.log(false || true);        // true
console.log(null || "default");    // "default"
console.log(undefined || "default"); // "default"
console.log(NaN || 0);             // 0

이 방식은 “빈 문자열도 입력되지 않은 값으로 처리하겠다”는 의도가 있을 때는 유용합니다.

하지만 0, false, ""도 정상적인 값으로 취급해야 하는 경우에는 문제가 될 수 있습니다.

3. 기존 예제 검증

원래 코드입니다.

function greetUser(name, greeting) {
  name = name || "User";
  greeting = greeting || "Hello";

  console.log(greeting + ", " + name + "!");
}

greetUser("Alice", "Hi"); // "Hi, Alice!" 출력
greetUser("", "Hi");      // "Hi, User!" 출력
greetUser("Alice");       // "Hello, Alice!" 출력

이 코드는 정상적으로 동작합니다.

다만 greetUser("", "Hi")에서 빈 문자열 ""은 falsy 값이므로 "User"로 대체됩니다.

즉, 이 코드는 다음 정책을 가진 코드입니다.

이름이 "", null, undefined, 0, false, NaN처럼 falsy 값이면 "User"로 처리한다.

이 정책이 의도한 것이라면 || 사용은 적절합니다.

4. ?? 연산자로 더 안전하게 기본값 설정하기

??는 nullish coalescing operator라고 부릅니다.

??는 왼쪽 값이 null 또는 undefined일 때만 오른쪽 기본값을 사용합니다. 0, false, ""는 그대로 유지합니다. (MDN Web Docs)

console.log("" ?? "Guest");        // ""
console.log(0 ?? 100);             // 0
console.log(false ?? true);        // false
console.log(null ?? "default");    // "default"
console.log(undefined ?? "default"); // "default"

||와 비교하면 차이가 명확합니다.

console.log("" || "Guest"); // "Guest"
console.log("" ?? "Guest"); // ""

console.log(0 || 100); // 100
console.log(0 ?? 100); // 0

console.log(false || true); // true
console.log(false ?? true); // false

따라서 값이 아예 없을 때만 기본값을 적용하고 싶다면 ||보다 ??가 더 적절합니다.

5. 기본 매개변수 사용하기

JavaScript 함수에서는 기본 매개변수를 사용할 수 있습니다.

function greetUser(name = "User", greeting = "Hello") {
  console.log(greeting + ", " + name + "!");
}

greetUser("Alice", "Hi"); // "Hi, Alice!"
greetUser("Alice");       // "Hello, Alice!"
greetUser();              // "Hello, User!"

기본 매개변수는 인자가 전달되지 않았거나 undefined가 전달된 경우에만 적용됩니다. (MDN Web Docs)

즉, 아래 코드에서는 기본값이 적용되지 않습니다.

function greetUser(name = "User") {
  console.log(name);
}

greetUser("");    // ""
greetUser(null);  // null
greetUser(false); // false
greetUser(0);     // 0

이 점이 || 방식과 가장 큰 차이입니다.

6. 상황별 추천 방식

상황 추천 방식
"", 0, false도 비어 있는 값으로 보고 기본값을 쓰고 싶을 때  ||
null, undefined일 때만 기본값을 쓰고 싶을 때 ??
함수 인자가 생략되었을 때만 기본값을 쓰고 싶을 때 기본 매개변수
빈 문자열, 공백 문자열, 타입 검증까지 직접 처리하고 싶을 때 명시적 조건문

7. 더 명확한 greetUser 예제

7-1. 빈 문자열도 기본 이름으로 처리하고 싶은 경우

function greetUser(name, greeting) {
  const displayName = name || "User";
  const displayGreeting = greeting || "Hello";

  console.log(`${displayGreeting}, ${displayName}!`);
}

greetUser("Alice", "Hi"); // "Hi, Alice!"
greetUser("", "Hi");      // "Hi, User!"
greetUser("Alice");       // "Hello, Alice!"

이 코드는 빈 문자열도 “입력 없음”으로 봅니다.

7-2. 빈 문자열은 유지하고, null 또는 undefined만 기본값 처리하는 경우

function greetUser(name, greeting) {
  const displayName = name ?? "User";
  const displayGreeting = greeting ?? "Hello";

  console.log(`${displayGreeting}, ${displayName}!`);
}

greetUser("Alice", "Hi"); // "Hi, Alice!"
greetUser("", "Hi");      // "Hi, !"
greetUser("Alice");       // "Hello, Alice!"
greetUser(null, "Hi");    // "Hi, User!"

이 코드는 빈 문자열 ""을 유효한 값으로 봅니다.

7-3. 공백 문자열까지 검사하는 실무형 예제

function greetUser(name, greeting) {
  const displayName =
    typeof name === "string" && name.trim() !== ""
      ? name.trim()
      : "User";

  const displayGreeting =
    typeof greeting === "string" && greeting.trim() !== ""
      ? greeting.trim()
      : "Hello";

  console.log(`${displayGreeting}, ${displayName}!`);
}

greetUser("Alice", "Hi");   // "Hi, Alice!"
greetUser("", "Hi");        // "Hi, User!"
greetUser("   ", "Hi");     // "Hi, User!"
greetUser(" Alice ", "Hi"); // "Hi, Alice!"
greetUser("Alice");         // "Hello, Alice!"

사용자 입력값을 처리하는 경우에는 이처럼 trim()까지 함께 고려하는 것이 더 안전합니다.

8. 유체 이름 변환 예제

다음 코드는 입력된 유체 이름을 소문자로 바꾼 뒤, 별칭을 표준 이름으로 변환하는 예제입니다.

원래 코드입니다.

function extractfluidstring(inputString) {
  let FluidName = inputString.toLowerCase();

  // Check for alternative names. Use lower case for all fluid names below.
  const fluidNameMap = {
    carbondioxide: "co2",
    carbonmonoxide: "co",
    c1: "methane",
    c2: "ethane",
    c3: "propane",
    ic4: "isobutane",
    c4: "butane",
  };

  FluidName = fluidNameMap[FluidName] || FluidName;
}

이 코드의 핵심 아이디어는 좋습니다.

하지만 실무에서는 다음 부분을 보완하는 것이 좋습니다.

  1. 함수명은 일반적으로 camelCase로 작성합니다.
  2. 변수명도 FluidName보다 fluidName이 JavaScript 관례에 더 맞습니다.
  3. inputStringnull 또는 undefined이면 toLowerCase()에서 오류가 발생합니다.
  4. "Carbon Dioxide", "carbon-dioxide", "carbon_dioxide" 같은 입력도 처리하려면 정규화가 필요합니다.
  5. 함수가 변환한 값을 return해야 재사용할 수 있습니다.

9. 개선된 유체 이름 변환 함수

function normalizeFluidInput(inputString) {
  return String(inputString ?? "")
    .trim()
    .toLowerCase()
    .replace(/[\s_-]+/g, "");
}

function extractFluidString(inputString) {
  const fluidName = normalizeFluidInput(inputString);

  const fluidNameMap = {
    carbondioxide: "co2",
    carbonmonoxide: "co",
    c1: "methane",
    c2: "ethane",
    c3: "propane",
    ic4: "isobutane",
    c4: "butane",
  };

  return fluidNameMap[fluidName] ?? fluidName;
}

사용 예제입니다.

console.log(extractFluidString("CarbonDioxide"));  // "co2"
console.log(extractFluidString("carbon dioxide")); // "co2"
console.log(extractFluidString("carbon-dioxide")); // "co2"
console.log(extractFluidString("carbon_monoxide")); // "co"
console.log(extractFluidString("C1"));             // "methane"
console.log(extractFluidString("c2"));             // "ethane"
console.log(extractFluidString("ic4"));            // "isobutane"
console.log(extractFluidString("water"));          // "water"
console.log(extractFluidString(null));             // ""
console.log(extractFluidString(undefined));        // ""

여기서 inputString ?? ""는 입력값이 null 또는 undefined일 때만 빈 문자열로 바꿉니다.

그리고 마지막 줄의 fluidNameMap[fluidName] ?? fluidName은 매핑된 값이 있으면 그 값을 반환하고, 없으면 원래 정규화된 이름을 반환합니다.

10. || 대신 ??를 사용한 이유

아래 코드도 현재 예제에서는 동작합니다.

return fluidNameMap[fluidName] || fluidName;

매핑 값이 모두 "co2", "methane"처럼 truthy 문자열이기 때문입니다.

하지만 나중에 매핑 값으로 빈 문자열 "", 0, false 같은 값이 들어갈 가능성이 있다면 ||는 의도치 않게 원래 값을 반환할 수 있습니다.

const map = {
  unknown: "",
};

const key = "unknown";

console.log(map[key] || key); // "unknown"
console.log(map[key] ?? key); // ""

따라서 객체 매핑 결과를 확인할 때는 ??가 더 의도를 명확하게 표현하는 경우가 많습니다.

11. 엄격한 검증이 필요한 경우

입력값이 비어 있으면 빈 문자열을 반환하는 대신 오류를 발생시키고 싶을 수도 있습니다.

function extractFluidStringStrict(inputString) {
  const fluidName = normalizeFluidInput(inputString);

  if (!fluidName) {
    throw new Error("fluid name is required");
  }

  const fluidNameMap = {
    carbondioxide: "co2",
    carbonmonoxide: "co",
    c1: "methane",
    c2: "ethane",
    c3: "propane",
    ic4: "isobutane",
    c4: "butane",
  };

  return fluidNameMap[fluidName] ?? fluidName;
}

사용 예제입니다.

console.log(extractFluidStringStrict("CarbonDioxide")); // "co2"
console.log(extractFluidStringStrict("C1"));            // "methane"

console.log(extractFluidStringStrict(""));
// Error: fluid name is required

입력값이 반드시 필요하다면 이처럼 명시적으로 오류를 발생시키는 편이 디버깅에 유리합니다.

12. 객체 옵션 기본값 예제

함수 옵션을 처리할 때도 기본값 설정은 자주 사용됩니다.

function createUser(options = {}) {
  const name = options.name ?? "Guest";
  const age = options.age ?? 0;
  const isAdmin = options.isAdmin ?? false;

  return {
    name,
    age,
    isAdmin,
  };
}

console.log(createUser());
// { name: "Guest", age: 0, isAdmin: false }

console.log(createUser({ name: "Alice", age: 0, isAdmin: false }));
// { name: "Alice", age: 0, isAdmin: false }

여기서 ??를 사용하면 age: 0, isAdmin: false 같은 값을 정상적인 값으로 유지할 수 있습니다.

만약 ||를 사용했다면 0false가 기본값으로 덮어써질 수 있습니다.

13. ??= 연산자 활용하기

객체에 기본값을 직접 채워 넣고 싶다면 ??= 연산자를 사용할 수도 있습니다.

??=는 왼쪽 값이 null 또는 undefined일 때만 오른쪽 값을 할당합니다. (MDN Web Docs)

function applyDefaultOptions(options) {
  options.name ??= "Guest";
  options.age ??= 0;
  options.isAdmin ??= false;

  return options;
}

console.log(applyDefaultOptions({ name: "Alice" }));
// { name: "Alice", age: 0, isAdmin: false }

console.log(applyDefaultOptions({ age: 0, isAdmin: false }));
// { age: 0, isAdmin: false, name: "Guest" }

||=도 있지만, 이 연산자는 왼쪽 값이 falsy일 때 할당합니다. 따라서 0, false, ""를 보존해야 하는 경우에는 ??=가 더 안전합니다. (GitHub)

14. 정리

사용자 입력처럼 빈 문자열도 잘못된 값으로 보고 싶다면 || 또는 명시적 조건문을 사용할 수 있습니다.

반대로 숫자 0, 불리언 false, 빈 문자열 ""도 정상적인 값으로 인정해야 한다면 ??를 사용하는 것이 더 안전합니다.


댓글 쓰기 · 수정

0 댓글