본문 바로가기
Developer/TIL

TIL : 250402수 (falsy값 처리(?., ??, || 언제 써야 할까?), setTimeout 0ms 비동기 처리, 프로그래머스 완주하지 못한 선수, for...in vs for...of 차이점 정리, 프로그래머스 실패율)

by 청량리 물냉면 2025. 4. 2.
반응형

falsy 값을 처리하는 방법

`TypeError: Cannot read properties of null (reading 'name')` 오류 발생

  • 이 오류는 null인 값에서 `name` 속성을 읽으려고 해서 발생한다.
  • 예를 들어, 아래 코드에서 menu가 null이면 에러가 발생함
console.log(menu.name); // menu가 null이면 오류 발생

 

 

해결 방법 4가지

✅ 1. `?.`(옵셔널 체이닝) 사용 (가장 추천!)

console.log(menu?.name); // menu가 null이면 undefined 반환 (오류 없음)

✔ `?.`(옵셔널 체이닝)을 사용하면 menu가 null이어도 오류 없이 undefined를 반환
✔ Vue 템플릿에서도 사용 가능

<p>{{ menu?.name }}</p> <!-- menu가 null이면 undefined로 처리됨 -->

 

✅ 2. `||`(논리 OR) 연산자로 기본값 설정

console.log(menu?.name || "기본값"); // menu가 null이면 "기본값" 출력

✔ null일 때 "기본값"을 대신 출력하도록 설정하면 안전함.

 

✅ 3. `??`(Nullish coalescing) 연산자로 기본값 설정

console.log(menu?.name ?? "기본값");

✔ `??`는 null 또는 undefined일 때만 "기본값"을 반환
✔ `||`와 다르게 0이나 ''(빈 문자열)는 유지됨

 

✅ 4. if 문으로 null 체크

if (menu) {
  console.log(menu.name);
} else {
  console.log("menu가 존재하지 않습니다.");
}

✔ 기본적인 if 문으로 null을 체크하고 안전하게 처리 가능

 

 

 

상황에 따른 연산자 사용

1. 옵셔널 체이닝 (`?.`) → "안전하게 접근할 때"

📌 객체의 속성이 null일 수도 있을 때 사용

✅ 언제 쓰나?

  • 객체가 null일 수도 있는 상황에서 안전하게 속성을 접근할 때
  • Vue 템플릿에서 안전하게 바인딩할 때
  • 중첩된 객체의 속성에 접근할 때

예제

const user = null; 

console.log(user?.name); // ❌ 오류 없이 undefined 반환
console.log(user?.profile?.age); // ❌ 중첩된 속성도 안전하게 처리

👉 user가 null이지만 `?.` 덕분에 undefined가 반환되어 오류가 나지 않음.

 

Vue 템플릿에서 사용 예시

<p>{{ user?.name }}</p> <!-- user가 null이어도 에러 안 남 -->

 

2. Null 병합 연산자 (`??`) → "null 또는 undefined일 때 기본값"

📌 값이 null 또는 undefined일 때만 기본값을 설정하고 싶을 때 사용

언제 쓰나?

  • null 또는 undefined일 때만 기본값을 제공하고 싶을 때
  • 0, ''(빈 문자열), false 같은 값은 유효하게 인정해야 할 때

예제

const value = null;
console.log(value ?? "기본값"); // "기본값" 출력 (value가 null이므로)

const count = 0;
console.log(count ?? 10); // 0 (기본값이 적용되지 않음)

👉 `??`는 null과 undefined만 기본값으로 대체하기 때문에, 0이나 ""(빈 문자열) 같은 값은 유지됨.

 

 

3. 논리 OR 연산자 (`||`) → "Falsy 값일 때 기본값"

📌 값이 false, 0, ""(빈 문자열), null, undefined이면 기본값을 설정할 때 사용

언제 쓰나?

  • "값이 없을 때" 기본값을 주고 싶을 때
  • null, undefined, 0, "", false를 전부 "없음"으로 간주할 때

예제

const name = "";
console.log(name || "기본값"); // "기본값" 출력 (빈 문자열은 false로 간주됨)

const count = 0;
console.log(count || 100); // 100 출력 (0은 false로 간주됨)

👉 `||`는 false, 0, "" 같은 값도 전부 기본값으로 대체해 버림.

 

📌 정리

객체 속성 접근할 때 `?.`
null/undefined에만 기본값 적용할 때 `??`
빈 값(0, "", false)까지 기본값 적용할 때 `||`

 

 

 

각 메뉴의 옵션 리스트가 열리지 않는 에러 수정

🔥 다른 곳을 클릭했을 때 `openedMenuIndex`가 항상 null로 초기화되는 현상 발생

문제 원인

현재 코드에서 `toggleMenu`함수를 실행하면 `openedMenuIndex`와 `openedMenuName`을 설정하는데, 이후 곧바로 `closeMenuOnClickOutside`함수가 실행되면서 값이 다시 null로 초기화되어 메뉴가 화면상에 뜨지 않을 수 있다. 
즉, 코드의 실행 순서에 따라 메뉴를 열자마자 닫혀버리는 문제가 발생하는 것이다.

 

발생 과정

  • 사용자가 특정 메뉴의 `⋮` 버튼을 클릭하여 `toggleMenu(menu.name, index)`가 실행됨.
  • `openedMenuIndex`와 `openedMenuName`이 변경됨.
  • 클릭 이벤트가 document에 전달되면서 곧바로 `closeMenuOnClickOutside`가 실행됨.
  • `closeMenuOnClickOutside`는 `openedMenuIndex`가 가리키는 `menuRefs` 요소에 `event.target`이 포함되지 않으면 `openedMenuIndex = null`로 설정.
  • 결과적으로, 메뉴가 열리자마자 닫히기 때문에 화면상에 옵션 리스트가 아예 나타나지 않는 것처럼 보임.

 

해결 방법: `setTimeout`을 사용하여 비동기 처리

이 문제는 `toggleMenu`가 실행된 후, `closeMenuOnClickOutside`가 너무 빨리 실행되면서 발생하는 것이므로 약간의 딜레이를 주어` toggleMenu`가 먼저 정상적으로 실행되도록 만들면 해결할 수 있다는 판단

const toggleMenu = (name, index) => {
  setTimeout(() => {
    openedMenuName.value = name;
    openedMenuIndex.value = index;
  }, 0);
};

왜 `setTimeout(() => {...}, 0)`인가?

    • `setTimeout`을 0ms로 설정하면 브라우저의 이벤트 루프에서 다음 이벤트 사이클에서 실행되도록 예약
    • 즉, setTimeout 안에 있는 코드는 이벤트 큐에 등록되고, 현재 실행 중인 모든 동기 코드가 완료된 다음 틱(next tick) 에 실행된다.
    • 이렇게 하면 `toggleMenu`의 코드가 완전히 실행된 후 `closeMenuOnClickOutside`가 실행되므로, `openedMenuIndex`가 의도대로 설정된 상태를 유지할 수 있다.
      • 버튼 클릭
      • `toggleMenu` 실행 → `setTimeout`으로 실행 예약
      • 클릭 이벤트 전파 → `closeMenuOnClickOutside` 실행 → 메뉴 닫힘
      • 현재 이벤트 루프 끝남
      • 이제 `setTimeout` 예약된 코드 실행 → `openedMenuIndex = index` 실행됨
      • 닫힌 메뉴가 다시 열리는 게 아니라, "닫히기 전에 메뉴를 연다"가 되는 것처럼 동작
  • 즉, 닫힘보다 나중에 실행되게끔 미루는 트릭이라고 볼 수 있다. 이러한 과정을 통해`setTimeout(..., 0)`으로 딜레이 없이 비동기 예약하면, 클릭 이벤트의 전파가 끝난 다음에 `openedMenuIndex`를 설정할 수 있고, 의도대로 메뉴를 열거나 상태를 바꿀 수 있게 되는 것이다.

 

👨‍💻 [프로그래머스] 완주하지 못한 선수

function solution(participant, completion) {
    var answer = '';
    let dic = {};
    answer = participant.reduce((dic, num, i) => {
        dic[num] ? dic[num]++ : dic[num] = 1;
        return dic;
    }, {});
    
    answer = completion.reduce((dic, num, i) => {
        dic[num] ? dic[num]-- : dic[num];
        if(!dic[num]) delete dic[num];
        return dic;
    }, answer);
    
    return Object.keys(answer)[0];
}
  • 최대 선수 10만 (입력개수)
  • `reduce`는 전체 배열을 순회하므로 `O(N)`의 시간복잡도를 가진다. `O(N)`은 1000만개의 입력을 커버할 수 있다.
  • 따라서 `reduce`를 두번 돌리더라도 시간 안에 해결할 수 있는 문제라 생각해서 위와 같이 문제를 풀었다. 

출처: 바킹독 시간복잡도

역시 다른 풀이를 살펴보니 훨씬 직관적인 풀이방법이 많았다😂

 

다른 풀이

function solution(participant, completion) {
    const map = new Map();

    for(let i = 0; i < participant.length; i++) {
        let a = participant[i], 
            b = completion[i];

        map.set(a, (map.get(a) || 0) + 1);
        map.set(b, (map.get(b) || 0) - 1);
    }

    for(let [k, v] of map) {
        if(v > 0) return k;
    }

    return 'nothing';
}

 

🔍 코드 분석

1. Map을 이용한 참가자 및 완주자 카운팅

const map = new Map();
  • Map 객체를 생성하여, 참가자와 완주자의 수를 기록하는 역할

2. 참가자와 완주자의 등장 횟수 기록

for(let i = 0; i < participant.length; i++) {
    let a = participant[i], 
        b = completion[i];

    map.set(a, (map.get(a) || 0) + 1);
    map.set(b, (map.get(b) || 0) - 1);
}
  • participant와 completion 배열을 같은 인덱스에서 하나씩 가져옴
  • map.set(a, (map.get(a) || 0) + 1);
    • 참가자를 map에 추가
    • map.get(a) || 0 → 기존 값이 없으면 0으로 초기화 후 +1
    • 즉, 참가자의 등장 횟수를 기록한다.
  • map.set(b, (map.get(b) || 0) - 1);
    • completion에 있는 이름의 등장 횟수를 -1 한다.
    • 완주자가 있으면 해당 참가자의 등장 횟수가 줄어든다.

 

3. 완주하지 못한 사람 찾기

for(let [k, v] of map) {
    if(v > 0) return k;
}
  • map을 순회하면서, 값(v)가 0보다 큰 사람을 찾는다.
  • 0보다 크다는 것은 참가했지만 완주하지 못했다는 뜻이므로, 그 이름(k)을 반환

4. 예외 처리

return 'nothing';
  • 만약 모든 사람이 완주했으면 "nothing"을 반환한다.
  • 그러나 문제에서 항상 한 명이 완주하지 못하는 조건을 제시했기 때문에, 실제 실행될 일은 거의 없음

🛠 예제 테스트

console.log(solution(["leo", "kiki", "eden"], ["eden", "kiki"])); 
// 결과: "leo"

console.log(solution(["maria", "tom", "jack", "maria"], ["jack", "maria", "tom"])); 
// 결과: "maria"

 

🚀 시간 복잡도 분석

  • for 루프가 participant.length번 실행 → O(N)
  • for 루프에서 Map을 조회하고 수정하는 연산은 평균적으로 O(1)
  • 최종적으로 O(N) + O(N) = O(N)

효율적인 풀이: O(N) 시간 복잡도로 해결할 수 있다.
(sort를 사용하는 풀이보다 더 빠름 → 정렬은 O(N log N)이므로)

 

 

 

🔍 for...in vs for...of 차이점 정리

구분  for...in for...of
사용 대상 객체(Object), 배열(Array) 배열(Array), 이터러블(Iterable)
반환값 키(인덱스 또는 프로퍼티명) 값 (배열 요소, 이터러블 값)
용도 객체 속성 순회, 배열 인덱스 순회 배열 요소 순회, Set, Map 등 이터러블 순회
성능 비교적 느림 (객체 속성 조회 포함) 빠름 (직접 값에 접근)
주의할 점 배열 순회 시 원치 않는 속성도 포함될 수 있음 Object에는 사용 불가능

 

1️⃣ for...in → 객체와 배열의 "키"를 순회

const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
    console.log(key, obj[key]); 
}
// 출력:
// a 1
// b 2
// c 3
  • for...in은 객체의 속성을 반복할 때 사용
  • 배열에도 사용 가능하지만, 인덱스(키)를 반환하므로 비추천

🔹 배열에서 사용 예시 (비추천)

const arr = ["apple", "banana", "cherry"];
for (let i in arr) {
    console.log(i, arr[i]);
}
// 출력:
// 0 apple
// 1 banana
// 2 cherry
  • i는 인덱스(0, 1, 2)를 반환

주의점: for...in은 배열에 사용하면 비효율적이며, 추가된 속성도 포함될 수 있음

Array.prototype.customProp = "test";

for (let i in arr) {
    console.log(i);  // 0, 1, 2, customProp (예상치 못한 값 포함)
}

 

2️⃣ for...of → 배열이나 이터러블의 "값"을 순회

const arr = ["apple", "banana", "cherry"];
for (let value of arr) {
    console.log(value);
}
// 출력:
// apple
// banana
// cherry
  • for...of는 배열 요소를 직접 가져옴 (값 반환)
  • Set, Map, String 등 이터러블(iterable) 객체에도 사용 가능

🔹 객체(Object)에는 사용 불가!

const obj = { a: 1, b: 2 };
for (let value of obj) { // ❌ TypeError: obj is not iterable
    console.log(value);
}

🔹 Set과 Map에서 사용

const set = new Set(["apple", "banana", "cherry"]);
for (let value of set) {
    console.log(value);
}
// apple
// banana
// cherry
const map = new Map([["a", 1], ["b", 2]]);
for (let [key, value] of map) {
    console.log(key, value);
}
// a 1
// b 2

 

정리

  • 객체(Object) 순회 → for...in (키 반환)
  • 배열(Array) 순회 → for...of (값 반환) ✅ 추천!
  • 이터러블(Set, Map 등) 순회 → for...of 사용 가능
  • 배열에서 for...in 사용 비추천 (불필요한 속성 포함 가능)

➡ 배열에는 for...of, 객체에는 for...in을 사용하자!

 

 

 

👨‍💻 [프로그래머스] 실패율

function solution(N, stages) {
    var answer = [];
    for(let i = 1; i <= N; i++){
        //시도한 사람
        const tried = stages.filter((it)=> it >= i).length;
        //실패한 사람
        const failed = stages.filter((it)=> it === i).length;
        
        answer.push([i, (failed/tried)]);
    }  
    
    answer.sort((a, b) => b[1] - a[1])
    return answer.map((it) => it[0]);
}

내일 다시 보기... 답안 보고 풀었음

반응형