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 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'4.Node.js > JavaScript&TypeScript' 카테고리의 다른 글
| [TypeScript] 8편. 개발자용 핵심 문법 & 실전 타입 설계 핸드북 (0) | 2025.12.29 |
|---|---|
| [TypeScript] 7편. 유틸리티 타입 이해하기: Partial, Readonly, Pick, Omit, 조건부 타입 활용법 (0) | 2025.12.24 |
| [TypeScript] 6편. 제네릭 (Generic) 이해하기: 함수, 인터페이스, 클래스, constraints 활용 (0) | 2025.12.22 |
| [TypeScript] 5편. 클래스에 타입 적용하기: 클래스 정의와 인터페이스 구현 (0) | 2025.12.18 |
| [TypeScript] 4편. Union, Literal, Intersection 타입 이해하기 : typeof, keyof, instanceof (0) | 2025.12.16 |