4.Node.js/JavaScript&TypeScript

[JavaScript] 11편. Java 개발자를 위한 JavaScript 핵심 연산자 정리

쿼드큐브 2025. 12. 30. 08:35
반응형
반응형

 

11편. Java 개발자를 위한 JavaScript 핵심 연산자 정리

 

📚 목차
1. 비교 연산의 기초: == vs === / Object.is
2. Truthy / Falsy와 단축 평가: || vs &&
3. 기본값 처리와 안전한 대체 전략: Nullish(??)
4. 안전한 접근과 조건부 실행: Optional Chaining(?.)
5. 타입·인스턴스·구조 판별 전략: typeof, instanceof, isArray, hasOwn
6. 데이터 구조 연산자 전략: 구조분해, Rest, Spread
7. 요약정리

 

삽화이미지
삽화이미지

 

📂 [GitHub 실습 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /JavaScript

 

1. 비교 연산의 기초: == vs === / Object.is

 

🔷 기본 비교: Equality (==) vs Strict Equality (===)

Loose Equality (==): 두 값의 값만 비교합니다. 타입이 다르면 자바스크립트가 자동으로 타입을 변환하여 비교합니다.
Strict Equality (===): 값과 데이터 타입이 모두 같아야 true를 반환합니다. 실무에서는 항상 이 연산자를 사용하는 것이 권장됩니다.

const a = 10;
const b = '10';

console.log("--- Equality ---");
console.log(a == b);  // true (문자열 '10'이 숫자 10으로 변환됨)

console.log("--- Strict Equality ---");
console.log(a === b); // false (숫자 vs 문자열, 타입이 다름)

// 특이 케이스
console.log(null == undefined);  // true
console.log(null === undefined); // false

 

🔷 숫자 비교의 예외: NaN, +0, -0 (IEEE 754)

수학적으로는 이해하기 어렵지만, 컴퓨터 과학(IEEE 754 표준) 관점에서 발생하는 특이 케이스들입니다.

NaN !== NaN 은 명세에 정의된 정상 동작
===는 +0과 -0을 동일한 값으로 취급

// 1. NaN은 자기 자신과도 같지 않다
const result = "apple" / 10; // NaN
console.log(result === NaN); // false

// 2. +0과 -0
console.log(+0 === -0); // true

 

🔷 더 정교한 비교: Object.is()

ES6에서 도입된 Object.is()는 ===보다 더 엄격한 '값 동일성(Same-value-equality)' 알고리즘을 사용합니다.

특히 NaN과 0의 부호를 구분해야 할 때 빛을 발합니다.

구분 ===(Strict) Object.is()
NaN vs NaN false true
+0 vs -0 true false
// React 같은 라이브러리에서 상태 변화를 감지할 때 내부적으로 사용합니다.
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0));   // false

NaN은 자기 자신과도 같지 않다는 IEEE-754 규칙을 ===는 그대로 따르지만, Object.is()는 “값 동일성(same-value)” 관점에서 NaN을 동일하다고 판단

===는 부호가 다른 0을 동일한 값으로 처리하지만, Object.is()는 부호까지 구분

 

🔷 실무 응용: 메서드마다 다른 비교 방식

사용하는 메서드에 따라 내부 비교 알고리즘이 다르다는 점을 반드시 기억해야 합니다

const box = [1, 5, NaN];

// indexOf는 ===를 사용하므로 NaN을 찾지 못함
console.log(box.indexOf(NaN)); // -1

// includes는 SameValueZero 알고리즘을 사용하여 NaN을 찾음
console.log(box.includes(NaN)); // true

 

2. Truthy / Falsy와 단축 평가: || vs &&

JavaScript에서 조건문(if, while, 논리 연산자 등)은 모든 값을 내부적으로 ToBoolean 추상 연산을 통해 true 또는 false로 변환합니다.

JavaScript의 논리 연산자는 실제로 평가된 값 자체를 반환합니다. boolean으로 변환하지 않습니다

 

🔷 외워두면 평생 써먹는 8가지 Falsy 값

자바스크립트에서 "이건 거짓이야"라고 판단하는 값은 딱 8개뿐입니다. 나머지는 전부 참(Truthy)입니다.

// 실습: Falsy 8인방 확인하기
if (!false)     console.log("1. false는 당연히 Falsy");
if (!0)         console.log("2. 숫자 0은 Falsy");
if (!-0)        console.log("3. 음수 0도 Falsy");
if (!0n)        console.log("4. BigInt 0도 Falsy");
if (!"")        console.log("5. 빈 문자열은 Falsy");
if (!null)      console.log("6. null은 Falsy");
if (!undefined) console.log("7. undefined는 Falsy");
if (!NaN)       console.log("8. NaN은 Falsy");

 

✔️ 주의해야 할 Truthy (흔히 하는 실수)

비어 있는 것처럼 보이지만 자바스크립트는 '참'으로 인식하는 값들입니다.

console.log(Boolean(" "));  // true (공백이 있는 문자열)
console.log(Boolean("0"));  // true (문자열 "0")
console.log(Boolean([]));   // true (빈 배열 - 매우 중요!)
console.log(Boolean({}));   // true (빈 객체)

// 실무 팁: 배열이 비었는지 확인할 때
let items = [];
if (items) { /* items가 빈 배열이어도 이 블록은 실행됨! */ }
if (items.length > 0) { /* 이렇게 체크해야 안전함 */ }

 

🔷 단축 평가 (Short-circuit Evaluation) 완벽 이해 : ||, &&

||(OR)와 &&(AND) 연산자는 불리언을 만드는 게 아니라, 피연산자 중 하나를 선택해서 반환합니다.

 

✔️ OR 연산자 (||): "첫 번째 Truthy를 찾아라"

왼쪽부터 검사하다가 처음으로 만나는 참(Truthy)인 값을 즉시 반환합니다. 모두 거짓이면 마지막 값을 반환합니다.

// 활용: 기본값 설정하기
function greet(name) {
    // name이 없거나 빈 문자열("")이면 "익명"을 할당
    const userName = name || "익명";
    console.log(`안녕하세요, ${userName}님!`);
}

greet("철수"); // "안녕하세요, 철수님!"
greet("");     // "안녕하세요, 익명님!" (빈 문자열은 Falsy이므로)

 

✔️ AND 연산자 (&&): "첫 번째 Falsy를 찾아라"

왼쪽부터 검사하다가 처음으로 만나는 거짓(Falsy)인 값을 즉시 반환합니다. 모두 참이면 마지막 값을 반환합니다.

// 활용: 조건부 실행 (에러 방지)
let user = {
    profile: { name: "민수" }
};

// user가 존재하고, user.profile이 존재할 때만 이름을 가져옴
const name = user && user.profile && user.profile.name;
console.log(name); // "민수"

let guest = null;
const guestName = guest && guest.name; // 에러 안 남! guest가 null이라 거기서 멈춤.
console.log(guestName); // null

 

✔️ || 연산자의 함정 (0이나 빈 문자열이 유효한 값일 때)

||는 0이나 ""도 Falsy로 취급하여 기본값을 덮어버립니다.

let fontSize = 0; 
let displaySize = fontSize || 16; 
console.log(displaySize); // 16 (0px로 설정하고 싶었으나 16이 나옴)

// 해결: null 병합 연산자 '??' (ES10+)
// null이나 undefined일 때만 뒤쪽 값을 선택함
let correctSize = fontSize ?? 16;
console.log(correctSize); // 0 (정상 출력!)

 

🔷 명시적 변환: Double Bang (!!)

값의 존재 여부를 확실하게 true 또는 false 타입으로 딱 박아서 전달하고 싶을 때 사용합니다.

const uploadFile = (file) => {
    const hasFile = !!file; // 파일이 있으면 true, null/undefined면 false
    console.log("파일 업로드 여부:", hasFile);
};

uploadFile(null);          // false
uploadFile({ name: "a.jpg" }); // true

 

3. 기본값 처리와 안전한 대체 전략: Nullish(??)

🔸 ||를 쓸 때:

"값이 비어있거나(null/undefined), 실패했거나(NaN), 0이거나, 비어있는 문자열이면 무조건 대체하자!" 할 때 사용합니다.
🔸 ??를 쓸 때:

"정말로 데이터가 할당되지 않았을 때만(null/undefined) 기본값을 쓰고, 나머지는 사용자가 입력한 대로 두자!" 할 때 사용합니다.

 

🔷 || 연산자의 함정: "Falsy"의 역설

자바스크립트에서 ||는 왼쪽 값이 Falsy(false, 0, "", null, undefined, NaN)인 경우 무조건 오른쪽 값을 반환합니다.

하지만 실무에서는 '값이 없는 상태'와 '값이 0인 상태'를 엄격히 구분해야 할 때가 많습니다.

// || 연산자의 문제점 재확인
function configureSetting(userValue) {
  const setting = userValue || "default";
  return setting;
}

console.log(configureSetting("custom"));    // "custom" ✓
console.log(configureSetting(undefined));   // "default" ✓
console.log(configureSetting(null));        // "default" ✓

// 하지만 다음 경우들은 문제입니다:
console.log(configureSetting(0));           // "default" (버그: 0은 유효한 값일 수 있음)
console.log(configureSetting(false));       // "default" (버그: false는 의도적 선택일 수 있음)
console.log(configureSetting(""));          // "default" (버그: 빈 문자열도 유효할 수 있음)

 

🔷 ?? (Nullish Coalescing): 진짜 "빈 값"만 찾아내기

?? 연산자는 오직 Nullish 값(null, undefined)일 때만 기본값을 선택합니다.
그 외의 값들은 그것이 비록 0이나 false일지라도 유효한 데이터로 인정하고 그대로 반환합니다.

// ?? 연산자의 정확한 동작
console.log(null ?? "default");       // "default"
console.log(undefined ?? "default");  // "default"

// 다른 falsy 값들은 그대로 유지됩니다
console.log(0 ?? "default");          // 0 ✓
console.log(false ?? "default");      // false ✓
console.log("" ?? "default");         // "" ✓
console.log(NaN ?? "default");        // NaN ✓

// || 연산자와 비교
console.log(0 || "default");          // "default" (0이 falsy로 처리됨)
console.log(0 ?? "default");          // 0 (0이 유효한 값으로 인정됨)

console.log(false || "default");      // "default"
console.log(false ?? "default");      // false

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

 

4. 안전한 접근과 조건부 실행: Optional Chaining(?.)

?. 앞의 값이 null이나 undefined이면, 뒤를 계산하지 않고 즉시 undefined를 반환합니다.

 

💡꼭 있어야 하는 값에는 ?.를 쓰지 않는 것이 좋습니다.

예를 들어 user.id가 반드시 존재해야 하는 로직인데 user?.id라고 쓰면, id가 없는 오류 상황에서도 에러 없이 undefined만 반환되어 어디서 문제가 생겼는지 디버깅하기 힘들어집니다.

 

✔️ 예시 1: API 응답 데이터 처리 (복잡한 중첩 구조)

서버에서 데이터를 받아올 때, 특정 필드가 누락되어도 프로그램이 멈추지 않게 하는 것이 핵심입니다.

// 서버에서 온 데이터 (일부 정보가 누락된 상태)
const apiResult = {
  status: 200,
  payload: {
    user: {
      profile: {
        nickname: "코딩왕",
        // SNS 정보는 아직 설정하지 않음
      }
    }
  }
};

// [실습] 아래 코드를 완성해 보세요.
// SNS의 인스타그램 아이디를 가져오되, 없으면 "아이디 없음"을 출력하세요.
const instaId = apiResult?.payload?.user?.profile?.sns?.instagram ?? "아이디 없음";
console.log(instaId); // 출력 결과: ?

 

✔️ 예시 2: 설정(Config) 객체 및 함수 실행

사용자가 설정값을 일부만 넘겼을 때, 기본값을 적용하고 콜백 함수를 안전하게 호출하는 패턴입니다.

function initializeApp(config) {
  // 1. 테마 설정 (없으면 "light" 테마 사용)
  const theme = config?.settings?.theme ?? "light";
  
  // 2. 초기화 완료 후 실행할 콜백 함수 (함수가 있을 때만 실행)
  // [실습] config 안에 onSuccess 함수가 있으면 호출하고, 없으면 조용히 넘어가는 코드를 작성해 보세요.
  config?.onSuccess?.();

  console.log(`App initialized with ${theme} mode.`);
}

initializeApp({ settings: { theme: "dark" } }); // onSuccess 없음
initializeApp(); // config 자체가 전달되지 않아도 에러 발생 X

 

✔️ 예시 3: 동적 리스트 및 검색 결과 처리

데이터가 배열인지 확인하기 번거로울 때 ?.를 사용하면 매우 편리합니다.

const searchResult = {
  items: null, // 검색 결과가 아직 로딩 중이거나 없는 경우
  totalCount: 0
};

// [실습] 첫 번째 검색 결과의 제목(title)을 가져오세요. 
// 데이터가 없다면 "검색 결과가 없습니다"가 출력되어야 합니다.
const firstTitle = searchResult?.items?.[0]?.title ?? "검색 결과가 없습니다";
console.log(firstTitle);

 

5. 타입·인스턴스·구조 판별 전략: typeof, instanceof, isArray, hasOwn

 

🔷 원시 타입은 typeof로 충분합니다

가장 가볍고 빠르며, 선언되지 않은 변수에도 에러를 내지 않아 안전합니다.

// 추천 케이스
typeof "Hello" === "string";    // true
typeof 123 === "number";        // true
typeof true === "boolean";      // true
typeof undefined === "undefined"; // true
typeof function(){} === "function"; // true

// 주의: 'null'과 'object'의 함정
typeof null === "object";       // true (언어 설계상의 오류)
typeof {} === "object";         // true
typeof [] === "object";         // true (배열도 객체로 취급)

// 실무 팁: null은 직접 비교하세요
if (value === null) { ... }

 

🔷 배열 판별의 표준: Array.isArray()
배열도 결국 object이기 때문에 typeof로는 구분할 수 없습니다.

const list = [1, 2, 3];

// 나쁜 예
typeof list === "object"; // true (일반 객체와 구분 불가)

// 좋은 예
Array.isArray(list); // true

 

🔷 복잡한 객체나 에러는 instanceof

어떤 '설계도(Class)'를 바탕으로 만들어졌는지 확인할 때 사용합니다.

// 1. 커스텀 클래스 판별
class User {}
const kim = new User();
console.log(kim instanceof User); // true

// 2. 에러 처리 (실무에서 가장 많이 쓰임)
try {
  throw new TypeError("타입 에러 발생!");
} catch (err) {
  if (err instanceof TypeError) {
    console.error("타입 관련 에러입니다.");
  } else if (err instanceof Error) {
    console.error("일반적인 에러입니다.");
  }
}

 

🔷 프로퍼티가 있는지 궁금할 때 (in vs hasOwn)

객체 안에 특정 키(key)가 있는지 확인할 때 사용합니다.

const person = { name: "Alice" };

// 1. in 연산자: "상속받은 것까지 다 뒤져라"
console.log("name" in person);      // true
console.log("toString" in person);  // true (Object에서 상속받음)

// 2. Object.hasOwn(): "네가 직접 가진 것만 말해라"
console.log(Object.hasOwn(person, "name"));     // true
console.log(Object.hasOwn(person, "toString")); // false

 

🔷 "끝판왕" 모든 타입을 정확하게 문자열로 뽑기

모든 자바스크립트 객체의 뿌리인 Object.prototype의 기능을 빌려오는 방식입니다. 어떤 타입이든 정확한 이름을 반환합니다.

function getExactType(value) {
  return Object.prototype.toString.call(value);
}

console.log(getExactType([]));        // "[object Array]"
console.log(getExactType(null));      // "[object Null]"
console.log(getExactType(new Date())); // "[object Date]"

 

6. 데이터 구조 연산자 전략: 구조분해, Rest, Spread

 

🔷객체 구조 분해 할당 (Object Destructuring)

기본 사용법에서 키 이름과 다른 변수명을 사용해야 할 때가 실무에서 자주 발생합니다. 이 내용을 추가하면 더 좋습니다.

const user = { id: 1, name: "Alice", email: "alice@example.com" };

// [실습 1] 기본 추출 및 변수명 변경
const { name, email, sns = "None" } = user; 
// sns처럼 객체에 없는 키는 기본값(None)이 적용됩니다.

const { name: userName } = user; // 'userName'이라는 이름의 변수로 할당 가능
console.log(userName); // "Alice"

// [실습 2] 주의: null vs undefined
const profile = { score: null, point: undefined };
const { score = 100, point = 50 } = profile;

console.log(score); // null (null은 값이라 기본값 100이 무시됨)
console.log(point); // 50 (undefined는 값이 없음을 뜻해 기본값이 적용됨)

 

💡실무 활용 (함수 인자 전달)

// 인자가 많아도 순서를 외울 필요가 없고, 필요한 값만 명시적으로 사용 가능합니다.
function displayUser({ name, email, age = "Unknown" }) {
  console.log(`${name}(${age})님, 이메일: ${email}`);
}

const userObj = { id: 7, name: "Sora", email: "sora@test.com" };
displayUser(userObj); // "Sora(Unknown)님, 이메일: sora@test.com"

 

🔷 배열 구조 분해 할당 (Array Destructuring)

배열은 키가 없으므로 인덱스(순서)에 따라 값이 할당됩니다.

const ranking = ["Gold", "Silver", "Bronze", "Iron"];

// [실습 1] 순서대로 추출하고 나머지는 무시하기
const [first, second] = ranking;
console.log(first); // "Gold"

// [실습 2] 건너뛰기 (콤마 사용)
const [top, , third] = ranking;
console.log(top, third); // "Gold", "Bronze"

// [실습 3] 변수 값 맞바꾸기 (Swap)
let a = "Coffee", b = "Tea";
[a, b] = [b, a]; 
console.log(a); // "Tea"

 

🔷 Rest 연산자 (...rest)

분해하고 남은 나머지 요소들을 하나의 덩어리(배열/객체)로 묶어줍니다. 항상 마지막에 위치해야 합니다.

// [실습 1] 객체에서의 Rest
const settings = { theme: "dark", fontSize: 16, language: "ko", alert: true };
const { theme, ...others } = settings;
console.log(others); // { fontSize: 16, language: "ko", alert: true }

// [실습 2] 함수의 파라미터 (Rest Parameter)
function sum(...numbers) {
  // 몇 개의 인자가 들어오든 numbers라는 '배열'로 취급합니다.
  return numbers.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

 

🔷 Spread 연산자 (...spread)

기존의 내용을 그대로 펼쳐서 새로운 배열/객체를 생성합니다. (복사 및 확장)

// [실습 1] 배열 합치기 및 문자열 사용 예시
const fastFood = ["Burger", "Fries"];
const fullMenu = [...fastFood, "Soda", "Coke"]; 
console.log(fullMenu); // ["Burger", "Fries", "Soda", "Coke"]

// [실습 2] 객체 업데이트 (불변성 유지)
const originalUser = { id: 1, name: "Gemini" };
const updatedUser = { ...originalUser, name: "Advanced Gemini" }; 
// 원본 originalUser는 수정되지 않고 보존됩니다.

// [실습 3] 주의: 얕은 복사 (Shallow Copy)
const group = { title: "Team A", members: ["Kim", "Lee"] };
const groupCopy = { ...group };

// 내부 배열(members)은 주소값이 복사되므로 원본과 복사본이 공유합니다.
groupCopy.members.push("Park"); 
console.log(group.members); // ["Kim", "Lee", "Park"] (원본도 함께 변경됨!)

 

7. 요약정리

연산자/ 문법 특징 / 내부 동작 예시
== 느슨한 비교: 암묵적 타입 변환 후 비교 10 == "10" → true
=== 엄격한 비교: 타입과 값이 모두 같아야 함 10 === "10" → false
Object.is() SameValue: NaN끼리 같음, +0/-0 다름 판정 Object.is(NaN, NaN) → true
Array.prototype.includes() SameValueZero: NaN 비교 가능, +0/-0 동일 판정 [NaN].includes(NaN) → true
|| 첫 번째 Truthy를 반환 (전부 Falsy면 마지막 값) "" || "default" → "default"
&& 첫 번째 Falsy를 반환 (전부 Truthy면 마지막 값) null && "A" → null
?? Nullish(null/undefined) 기준 기본값 선택 0 ?? 10 → 0
||= 왼쪽이 Falsy일 때만 오른쪽 값 대입 a ||= 10
&&= 왼쪽이 Truthy일 때만 오른쪽 값 대입 a &&= 10
??= 왼쪽이 Nullish이면 오른쪽 대입 a ??= 10
?. 왼쪽이 Nullish이면 접근·호출을 중단하고 undefined 반환 obj?.prop, obj?.method()
!! 값을 Boolean 타입으로 강제 변환 !!"x" → true
typeof 기본 타입 문자열 반환 typeof null → "object"
instanceof 프로토타입 체인 내 생성자 존재 여부 확인 err instanceof Error
Array.isArray() 객체가 진짜 배열인지 판별 Array.isArray([]) // true
Object.hasOwn(obj, key) 상속 제외, 객체 본인 소유 프로퍼티만 확인 Object.hasOwn(obj, "key")
객체 구조 분해 {} 키 기준으로 값 추출. undefined일 때만 기본값 적용(null은 값) const {a=1} = {a:null} → a는 null
배열 구조 분해 [] 인덱스 순서 기준 값 추출 const [a, b] = [1, 2]
Rest (...rest) 분해 후 남은 요소 수집(마지막 위치만) const [a, ...rest] = [1, 2, 3]
Spread (...spread) 기존 요소를 낱개로 펼쳐서 복사 (얕은 복사) const newObj = {...oldObj}

 


※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형

 

반응형