Vanilla JS에서 변수 할당 방법이 다양해서 한 번 정리해 보고자 합니다.

정리 요약

상황별 할당 방식 선택

상황 권장 방식 예시
단순 변수 선언 const, let const name = "Alice"
객체에서 특정 속성 추출 구조 분해 const { email } = user
함수 옵션 처리 구조 분해 + 기본값 function({ retry = true } = {})
기존 값 보존 + 기본값 설정 Null 병합 할당 obj.timeout ??= 5000
Falsy 제외하고 기본값 OR 할당 `config.theme
조건에 따른 업데이트 AND 할당 user.isActive &&= updateSession()
안전한 속성 접근 선택적 체이닝 user?.profile?.name
조건부 단일 값 삼항 연산자 const status = age >= 19 ? "Adult" : "Minor"

기본 할당 (Basic Assignment)

가장 기초적인 선언 및 할당 방식입니다. 할당 연산자(=)는 우측 피연산자의 값을 좌측 피연산자(변수 또는 속성)에 할당합니다.

// 선언과 동시에 할당
const name = "Junie"; 
let count = 0;

// 나중에 할당
let score;
score = 100;

// 할당식 자체도 값을 반환함
let x = 2;
console.log((x = 3 + 1));  // 4 출력 (할당값이 표현식의 결과)

// 여러 변수에 같은 값 할당
let a, b, c;
a = b = c = 5;  // 우측에서 좌측으로 평가됨
console.log(a, b, c);  // 5 5 5

주의: 선언 키워드 없이 여러 변수를 체이닝하면 의도와 다를 수 있습니다.

// 문제가 있는 코드
const x = y = z = 10;
// x만 const로 선언됨
// y와 z는 비선언 변수 (strict mode에서는 ReferenceError)

구조 분해 할당 (Destructuring Assignment)

객체나 배열의 속성을 해체하여 개별 변수에 즉시 할당하는 강력한 방식입니다. 특히 함수 매개변수나 복잡한 객체에서 필요한 값만 추출할 때 유용합니다.

객체 구조 분해 (Object Destructuring)

const config = {
  url: "https://api.example.com",
  method: "POST",
  timeout: 5000,
  headers: { "Content-Type": "application/json" }
};

// 1. 필요한 속성만 추출
const { url, method } = config;
console.log(url, method);  // "https://api.example.com" "POST"

// 2. 다른 이름으로 할당 (변수명 별칭)
const { timeout: requestTimeout } = config;
console.log(requestTimeout);  // 5000

// 3. 기본값 설정 (undefined일 때만 적용)
const { retryOnUnauthorized = true, auth = "Bearer token" } = config;
console.log(retryOnUnauthorized);  // true (config에 없으므로 기본값)

// 4. 중첩 객체 구조 분해
const { headers: { "Content-Type": contentType } } = config;
console.log(contentType);  // "application/json"

// 5. 나머지 속성 수집 (REST 패턴)
const { url, ...rest } = config;
console.log(rest);  // { method: "POST", timeout: 5000, headers: {...} }

// 6. 선언 없이 할당 (기존 변수에 값 할당)
let a, b;
({ a, b } = { a: 1, b: 2 });  // 괄호 필수 ({ } 블록과 구분)
console.log(a, b);  // 1 2

배열 구조 분해 (Array Destructuring)

const colors = ["#FF0000", "#00FF00", "#0000FF"];

// 1. 순서대로 할당
const [red, green, blue] = colors;
console.log(red, green, blue);  // "#FF0000" "#00FF00" "#0000FF"

// 2. 일부 생략
const [primary, , tertiary] = colors;
console.log(primary, tertiary);  // "#FF0000" "#0000FF"

// 3. 나머지 요소 할당
const [first, ...rest] = colors;
console.log(first, rest);  // "#FF0000" ["#00FF00", "#0000FF"]

// 4. 기본값 설정
const [a = "default"] = [];
console.log(a);  // "default"

// 5. 값 교체 (스왑)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y);  // 2 1 (임시 변수 불필요)

// 6. 정규식 exec() 결과 분해
const url = "https://example.com:8080";
const [, protocol, host] = /^(\w+):\/\/([^:]+)/.exec(url);
console.log(protocol, host);  // "https" "example.com"

중첩 구조 분해

const data = {
  user: {
    name: "Alice",
    profile: {
      age: 28,
      location: "Seoul"
    }
  },
  posts: [
    { id: 1, title: "First Post" },
    { id: 2, title: "Second Post" }
  ]
};

// 깊은 중첩 추출
const { 
  user: { 
    profile: { location }
  },
  posts: [{ title: firstTitle }]
} = data;

console.log(location, firstTitle);  // "Seoul" "First Post"

복합 대입 연산자 (Compound Assignment Operators)

연산 후 결과를 다시 변수에 할당하는 축약 형태입니다. x += yx = x + y와 동일합니다.

let count = 5;

// 산술 연산자
count += 3;   // count = count + 3 → 8
count -= 2;   // count = count - 2 → 6
count *= 2;   // count = count * 2 → 12
count /= 3;   // count = count / 3 → 4
count %= 3;   // count = count % 3 → 1
count **= 2;  // count = count ** 2 → 1

// 문자열 연결 (+=만 해당)
let message = "Hello";
message += " World";  // "Hello World"

// 비트 연산자
let flags = 5;        // 0101
flags &= 3;           // 0101 & 0011 = 0001 → 1
flags |= 4;           // 0001 | 0100 = 0101 → 5
flags ^= 2;           // 0101 ^ 0010 = 0111 → 7
flags <<= 1;          // 0111 << 1 = 1110 → 14
flags >>= 2;          // 1110 >> 2 = 0011 → 3

// BigInt도 지원
let big = 10n;
big += 5n;  // 15n
// big += 5;  // TypeError: BigInt와 일반 숫자 혼용 불가

주의: +=의 동작은 피연산자의 타입에 따라 결정됩니다.

let result = "5";
result += 3;      // "53" (문자열 연결, 숫자 연산 아님)

result = 5;
result += 3;      // 8 (숫자 연산)

함수 매개변수에서의 할당

함수가 호출될 때 인자를 받아들이는 과정 자체가 할당입니다.

기본 매개변수 (Default Parameters)

// ES5 방식 (조건문)
function fetchApiES5(url, method) {
  method = method || "GET";
  console.log(`${method} request to ${url}`);
}

// ES6+ 방식 (더 권장)
function fetchApi(url, method = "GET") {
  console.log(`${method} request to ${url}`);
}

fetchApi("/users");              // "GET request to /users"
fetchApi("/users", "POST");      // "POST request to /users"
fetchApi("/users", undefined);   // "GET request to /users" (undefined는 기본값 트리거)

매개변수 구조 분해 + 기본값

실무에서 가장 많이 쓰는 패턴입니다. 함수 호출 시 옵션 객체를 받으면서 동시에 개별 변수로 분해합니다.

// 구조 분해 + 기본값 + 나머지 수집
function request({
  url,
  method = "GET",
  timeout = 5000,
  retryOnUnauthorized = true,
  ...otherOptions
}) {
  console.log(`${method} ${url}`);
  console.log(`Retry enabled: ${retryOnUnauthorized}`);
  console.log(`Other options:`, otherOptions);
}

// 호출
request({ 
  url: "/users",
  data: { name: "Alice" }
});

// 출력:
// GET /users
// Retry enabled: true
// Other options: { data: { name: "Alice" } }

인자가 전달되지 않을 때의 기본 객체

// 문제: 인자 없이 호출하면 TypeError
function config1({ theme }) {
  console.log(theme);
}
// config1();  // TypeError: Cannot destructure property 'theme' of undefined

// 해결: 기본 매개변수로 빈 객체 제공
function config2({ theme } = {}) {
  console.log(theme);  // undefined
}
config2();  // 에러 없음

function config3({ theme = "dark" } = {}) {
  console.log(theme);  // "dark"
}
config3();  // "dark"

논리 할당 연산자 (Logical Assignment)

조건문과 할당을 한 번에 처리하여 코드를 매우 간결하게 만듭니다. ES2021+에서 도입되었습니다.

1. 논리 OR 할당 (||=)

좌측이 falsy 값일 때만 우측을 평가하고 할당합니다. 조건부로 기본값을 설정할 때 사용합니다.

let config = { theme: "dark" };

// config.fontSize가 undefined, null, 0, false, ""이면 16 할당
config.fontSize ||= 16;
console.log(config.fontSize);  // 16

// 이미 값이 있으면 할당하지 않음 (우측 표현식도 평가 안 함)
config.theme ||= "light";
console.log(config.theme);  // "dark" (변경 안 됨)

// 부작용 있는 함수도 short-circuit됨
function getDefault() {
  console.log("Called");
  return "light";
}
config.theme ||= getDefault();  // "Called" 출력 안 함 (theme이 truthy이므로)

2. 논리 AND 할당 (&&=)

좌측이 truthy 값일 때만 우측을 평가하고 할당합니다. 조건이 참일 때만 값을 업데이트할 때 사용합니다.

let user = { 
  name: "Bob",
  isLoggedIn: true 
};

// isLoggedIn이 true면 새로운 세션 정보 할당
user.sessionId &&= generateSessionId();  // 할당됨

user = { 
  name: "Alice",
  isLoggedIn: false 
};

user.sessionId &&= generateSessionId();  // 할당 안 됨 (isLoggedIn이 falsy)

// DOM 요소 업데이트 예제
let element = document.querySelector(".user-card");
// element가 존재하면만 스타일 업데이트 (조건문 대체)
element &&= (element.style.display = "block");

3. Null 병합 할당 (??=)

좌측이 null 또는 undefined일 때만 우측을 평가하고 할당합니다. 0, "", false 같은 falsy 값은 유효한 값으로 취급합니다.

let settings = {};

// 1. null 병합 할당 사용
settings.volume ??= 50;
settings.volume ??= 100;  // volume이 이미 50이므로 할당 안 됨

// 2. OR 할당과의 차이
let item = { count: 0 };

item.count ||= 10;       // 0은 falsy → 10 할당 (문제!)
console.log(item.count); // 10

let item2 = { count: 0 };
item2.count ??= 10;      // 0은 null/undefined가 아님 → 할당 안 함 (올바름)
console.log(item2.count); // 0

// 3. 실무 예제
let userCount;
userCount ??= 100;  // undefined → 100 할당

let cachedData = null;
cachedData ??= fetchDataFromServer();  // null → 서버에서 데이터 가져옴

let timeout = "";
timeout ??= 5000;  // ""는 유효한 값 → 할당 안 함 (의도대로)
timeout ||= 5000;  // ""는 falsy → 5000 할당 (다를 수 있음)

Short-Circuit 동작

모든 논리 할당 연산자는 short-circuit 처리됩니다. 할당이 일어나지 않으면 우측 표현식을 평가하지 않습니다.

// 성능: 비용이 큰 함수 호출 방지
function expensiveOperation() {
  console.log("Expensive operation running");
  return 1000;
}

let cache = { value: 50 };
cache.value ||= expensiveOperation();  // "Expensive operation running" 출력 안 함

let empty = {};
empty.value ||= expensiveOperation();  // "Expensive operation running" 출력 됨 (평가됨)

조건부 및 비동기 할당

프로그램의 흐름에 따라 결과값을 변수에 담습니다.

삼항 연산자를 통한 할당

const age = 25;
const status = (age >= 19) ? "Adult" : "Minor";
console.log(status);  // "Adult"

// 중첩된 삼항 연산자 (가독성 주의)
const category = 
  score >= 90 ? "A" :
  score >= 80 ? "B" :
  score >= 70 ? "C" : "F";

선택적 체이닝 (Optional Chaining)

객체가 존재하지 않을 때 에러를 내지 않고 undefined를 할당합니다.

const user = {
  profile: {
    address: "Seoul"
  }
};

// 전통적인 방식 (방어적 코드)
const street = user && user.profile && user.profile.address;

// 선택적 체이닝 (더 간결)
const street = user?.profile?.address;
console.log(street);  // "Seoul"

// 존재하지 않는 경로
const country = user?.profile?.country;
console.log(country);  // undefined (에러 아님)

// 메소드 호출
user?.profile?.printAddress?.();  // address 함수가 있으면 호출

// 배열 접근
const firstItem = user?.items?.[0];

// Null 병합과 조합
const displayName = user?.name ?? "Guest";

비동기 결과 할당

// Promise then 체인
const fetchUser = () => fetch("/api/user").then(res => res.json());

const user = await fetchUser();
console.log(user);  // 유저 객체

// 여러 비동기 작업 병렬 처리
const [profile, settings, posts] = await Promise.all([
  fetchProfile(),
  fetchSettings(),
  fetchPosts()
]);

// try-catch와 함께
let data;
try {
  data = await fetchData();
} catch (error) {
  data = {};  // 에러 시 기본값
}

// 선택적 체이닝 + 비동기
const email = await user?.getEmail?.();  // user가 없거나 메소드 없으면 undefined

객체 리터럴 단축 및 계산된 속성

객체를 생성하면서 값을 채워넣는 방식입니다.

단축 속성명 (Shorthand Property)

const id = "admin";
const name = "Alice";
const role = "manager";

// 전통적인 방식
const user1 = {
  id: id,
  name: name,
  role: role
};

// 단축 속성명 (변수명 = 속성명일 때)
const user2 = { id, name, role };

// 혼합 사용
const user3 = {
  id,
  name,
  role,
  createdAt: new Date()  // 다른 값 혼합 가능
};

console.log(user2.id);  // "admin"

계산된 속성명 (Computed Property)

const prefix = "user";
const suffix = "key";

const storage = {
  [prefix + "_" + suffix]: true,  // "user_key": true
  [`${prefix}_id`]: 12345,         // "user_id": 12345
  [Symbol.iterator]: function() {  // 심볼 사용도 가능
    return this;
  }
};

console.log(storage["user_key"]);  // true
console.log(storage.user_id);      // 12345

메소드 단축

const obj = {
  // 전통적인 방식
  method1: function() {
    return "result";
  },
  
  // 단축 메소드
  method2() {
    return "result";
  },
  
  // 화살표 함수는 단축 불가
  method3: () => "result"
};

obj.method2();  // "result"

최신 기능 브라우저 지원

기능 도입 연도 비고
구조 분해 할당(Destructuring) ES2015 (ES6) 모든 주요 브라우저 지원
기본 매개변수(Default parameters) ES2015 모든 주요 브라우저 지원
논리 할당 연산자 (Logical assignment)
(||=,&&=,??=)
ES2021 IE 미지원
선택적 체이닝(Optional chaining)
(?.)
ES2020 IE 미지원
Null 병합(Nullish coalescing)
(??)
ES2020 IE 미지원
계산된 속성명(Computed property names) ES2015 모든 주요 브라우저 지원

성능 고려사항

  1. Short-circuit 활용: 논리 할당 연산자는 조건에 따라 우측 평가를 건너뜀으로 성능 최적화
  2. 구조 분해 오버헤드: 매우 큰 객체에서는 필요한 속성만 추출하기
  3. 재할당 주의: const는 재할당 불가, let은 가능하지만 예측 가능한 범위에서만 사용

일반적인 실수 및 해결법

// 1. 구조 분해 시 기본값 누락
// 잘못된 예
const { timeout } = options;  // options.timeout이 undefined면 timeout도 undefined

// 올바른 예
const { timeout = 5000 } = options;

// 2. 객체 구조 분해 선언 없이 시작
// 잘못된 예 (블록 문으로 해석됨)
{ url, method } = config;

// 올바른 예
({ url, method } = config);

// 3. 0이나 빈 문자열 기본값 처리
// 잘못된 예 (||= 사용)
settings.width ||= 100;  // width: 0은 falsy라 100으로 덮어씀

// 올바른 예 (??= 사용)
settings.width ??= 100;  // width: 0은 유지, undefined만 100으로 설정

// 4. BigInt와 일반 숫자 혼용
// 잘못된 예
let big = 10n;
big += 5;  // TypeError

// 올바른 예
big += 5n;