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

2025. 4. 2. 22:29·Developer/TIL
반응형

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]);
}

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

반응형
저작자표시 비영리 변경금지 (새창열림)

'Developer > TIL' 카테고리의 다른 글

TIL : 250408화 (flex-basis: 플렉스 아이템의 초기 크기 지정, 프로그래머스 성격 유형 검사하기)  (0) 2025.04.08
TIL : 250404금 (프로그래머스 다트 게임, Git 원격 브랜치 코드 로컬 브랜치에 적용하기, 자바스크립트 소수점 자르기 방법, 디버거 사용법, JS 딕셔너리)  (0) 2025.04.04
TIL : 250403목 (프로그래머스 크레인 인형뽑기 게임, debugger의 중요성...)  (0) 2025.04.04
TIL : 250401화 (:active, 즐겨찾기 기능 css 변경 진행중-event.target, menuRefs, white-space, 프로그래머스 문자열 내림차순으로 배치하기)  (0) 2025.04.01
TIL : 250331월 (프로그래머스 해시, 윤년, 같은 숫자는 싫어, 문자열 내 마음대로 정렬하기(sort()함수에 대해 알아보기))  (1) 2025.03.31
TIL : 250328금 (메뉴 검색창 만들기, 즐겨찾기 기능 구현, 프로그래머스 유연근무제)  (0) 2025.03.29
'Developer/TIL' 카테고리의 다른 글
  • TIL : 250404금 (프로그래머스 다트 게임, Git 원격 브랜치 코드 로컬 브랜치에 적용하기, 자바스크립트 소수점 자르기 방법, 디버거 사용법, JS 딕셔너리)
  • TIL : 250403목 (프로그래머스 크레인 인형뽑기 게임, debugger의 중요성...)
  • TIL : 250401화 (:active, 즐겨찾기 기능 css 변경 진행중-event.target, menuRefs, white-space, 프로그래머스 문자열 내림차순으로 배치하기)
  • TIL : 250331월 (프로그래머스 해시, 윤년, 같은 숫자는 싫어, 문자열 내 마음대로 정렬하기(sort()함수에 대해 알아보기))
청량리 물냉면
청량리 물냉면
프로그래밍 공부를 하고 있습니다. 공부 내용 정리 겸 정보 공유를 목적으로 합니다.
    반응형
  • 청량리 물냉면
    노력중인 블로그
    청량리 물냉면
  • 전체
    오늘
    어제
    • 분류 전체보기 (506)
      • 프로그래밍 (41)
        • Programming (1)
        • C | C++ (6)
        • Java (28)
        • Python (5)
      • 웹 프로그래밍 (2)
        • HTML | CSS (5)
        • JavaScript | TypeScript (41)
        • React (25)
        • Vue.js (0)
        • Next.js (18)
        • Spring & Spring Boot (13)
        • JSP & Servlet (1)
        • DB (4)
      • 웹 프로젝트 (77)
        • 웹 프로젝트 (22)
        • 🥨스낵몰 (3)
        • 👨‍👨‍👧‍👧소셜 가계부 (26)
        • 🌜꿈 일기장 (11)
        • 🔮포트폴리오 사이트 (11)
        • 🏃‍♂️팀 프로젝트: 일정관리 프로그램 (0)
        • 📈팀 프로젝트: AI기반 주식 분석 플랫폼 (0)
        • 😺Just Meow It: 조언 사이트 (2)
        • 📕Workly: 교대근무 다이어리 (1)
      • 앱 프로그래밍 (26)
        • Flutter (24)
        • Kotlin (2)
      • Problem Solving (166)
        • 백준 (52)
        • 프로그래머스 (79)
        • SWEA (29)
      • Computer Science (40)
        • 알고리즘 (14)
        • 컴퓨터 네트워크 (18)
        • 이산수학 (8)
      • Developer (47)
        • 후기 (4)
        • 자료정리 (4)
        • 취업 | 취준 (9)
        • SSAFY (1)
        • 웹개발 교육 프로그램 (9)
        • TIL (20)
  • 블로그 메뉴

    • 홈
    • Github
  • 공지사항

    • 프로그래밍 공부 중😊
  • 인기 글

  • 태그

    프로젝트
    자바스크립트
    컴퓨터네트워크
    타입스크립트
    블로그 제작
    리액트
    알고리즘
    구현
    Jiraynor Programming
    포트폴리오
    Next.js
    SWEA
    ZeroCho
    공식문서
    bfs
    파이썬
    자바
    React
    강의내용정리
    백준
    AWS
    d3
    클론 프로젝트
    프로그래머스
    mysql
    Til
    웹사이트
    플러터
    spring boot
    뉴렉처
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
청량리 물냉면
TIL : 250402수 (falsy값 처리(?., ??, || 언제 써야 할까?), setTimeout 0ms 비동기 처리, 프로그래머스 완주하지 못한 선수, for...in vs for...of 차이점 정리, 프로그래머스 실패율)
상단으로

티스토리툴바