Modern JavaScript

변수와 스코프 (let, const)

var는 버그를 유발하기 쉬워 더 이상 사용하지 않음. 혹 동료가 var를 사용하고 있다면 거침없는 하이킥으로..

const maxPoints = 100; // 변경 불가
let currentScore = 0;  // 변경 가능
currentScore += 10;

객체 초기화 단축 성격 (Property Shorthand)

const name = 'Sora';
const age = 28;

// 과거 방식
const user = { name: name, age: age };

// 최신 방식 (똑같이 동작합니다)
const user = { name, age };

구조를 분해해서 할당 (구조분해할당, Destructuring Assignment)

const user = { name: 'Alice', age: 30, city: 'Seoul' };

// 객체 구조 분해
const { name, age } = user;
console.log(name, age); // Alice 30

const { name: name2, ...sss } = user;
console.log(name2, sss); // Alice { age: 30, city: 'Seoul' }

// 다른 이름으로 할당하거나 기본값 설정도 가능
const { name: fullName, email = 'noemail@example.com' } = user;
console.log(fullName, email); // Alice noemail@example.com

// 배열 구조 분해
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third); // red green blue

화살표 함수 (Arrow Functions)

화살표 함수는 this에 대한 유지력이 발생

// 기존 방식
function add(a, b) {
  return a + b;
}

// 화살표 함수 (최신)
const add = (a, b) => a + b;

구성 요소를 펼쳐 사용하는 문법 (스프레드 문법, Spread Syntax)

/* 배열 결합 및 복사에서의 예 */

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 배열 결합
const combinedArr = [...arr1, ...arr2];
console.log(combinedArr); // [1, 2, 3, 4, 5, 6]

// 배열 복사 (얕은 복사)
const copiedArr = [...arr1];
console.log(copiedArr); // [1, 2, 3]

/* 객체 병합 및 복사에서의 예 */

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

// 객체 병합
const combinedObj = { ...obj1, ...obj2 };
console.log(combinedObj); // { a: 1, b: 2, c: 3, d: 4 }

// 객체 복사 (얕은 복사)
const copiedObj = { ...obj1 };
console.log(copiedObj); // { a: 1, b: 2 }

/* 함수 인자 전달에서의 예 */

function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6 (배열 요소를 개별 인자로 펼쳐서 전달)

// 나머지: 모으기
function sum2(first, ...rest) {
  return rest.reduce((acc, v) => acc + v, first);
}

console.log(sum2(1, 2, 3, 4)); // 출력 : 10

null 또는 undefine이 아닐 경우에 대한 선택적 체이닝(선택적 체이닝, Optional Chaining)

const user = {
  name: 'Dave',
  address: {
    street: '123 Main St',
    city: 'Busan'
  },
  // phone: '010-1234-5678' // phone 속성이 없을 수 있음
};

// 기존 방식 (에러 방지)
let city = user.address && user.address.city;
console.log(city); // Busan

let zipCode;
if (user.address && user.address.zipCode) {
  zipCode = user.address.zipCode;
}
console.log(zipCode); // undefined

// 선택적 체이닝
const userCity = user.address?.city;
console.log(userCity); // Busan

const userPhone = user.phone?.number; // user.phone이 undefined이므로 userPhone은 undefined
console.log(userPhone); // undefined

const userStreet = user.address?.street;
console.log(userStreet); // 123 Main St

// 배열 요소에도 적용 가능
const arr = [{ value: 10 }];
const firstValue = arr?.[0]?.value;
console.log(firstValue); // 10

const emptyArr = [];
const nonExistentValue = emptyArr?.[0]?.value;
console.log(nonExistentValue); // undefined

// 매서드도 가능함
console.log?.("....");

진짜 null 또는 undefine에 대한 선택 연산자 (Nullish 병합 연산자, Nullish Coalescing Operator)

const userInput = null;
const defaultValue = 'default value';

// || 연산자 (Falsy 값도 걸러냄)
const resultOr = userInput || defaultValue;
console.log(resultOr); // default value

const zeroValue = 0;
const resultOrZero = zeroValue || defaultValue;
console.log(resultOrZero); // default value (0도 Falsy로 간주하여 default value가 할당됨)

// ?? 연산자 (null 또는 undefined만 걸러냄)
const resultNullish = userInput ?? defaultValue;
console.log(resultNullish); // default value

const zeroValueNullish = 0;
const resultNullishZero = zeroValueNullish ?? defaultValue;
console.log(resultNullishZero); // 0 (0은 유효한 값으로 간주됨)

const emptyString = '';
const resultNullishEmptyString = emptyString ?? defaultValue;
console.log(resultNullishEmptyString); // ''

비동기 처리 ( async / await )

async function fetchData() {
  try {
    const res = await fetch("https://api.example.com/data");
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

최신 배열 / 객체 메서

// 배열
[1, 2, 3].at(-1); // 3 (뒤에서 인덱스)
[1, [2, [3]]].flat(Infinity); // [1, 2, 3]
[1, 2, 3].flatMap(x => [x, x * 2]); // [1,2, 2,4, 3,6]
// 객체
Object.entries({ a: 1, b: 2 }); // [["a",1], ["b",2]]
Object.fromEntries([["a", 1]]); // { a: 1 }
Object.hasOwn(obj, "key"); // true/false (ES2022)

기존 .sort()나 .reverse()는 원본 배열 자체를 바꾸어 버려서 버그를 자주 유발했습니다. 최신 메서드는 원본은 그대로 두고, 정렬된 ‘새로운 배열’을 반환합니다.

const numbers = [3, 1, 4];
const sorted = numbers.toSorted();

console.log(numbers); // [3, 1, 4] (원본 유지!)
console.log(sorted);  // [1, 3, 4] (새 배열)

논리 할당 연산자

a ||= "기본값"; // a가 falsy면 할당
a &&= "새값"; // a가 truthy면 할당
a ??= "기본값"; // a가 null/undefined면(truthy와 다름) 할당

Promise 관련

// 모두 성공해야 통과
await Promise.all([p1, p2, p3]);
// 하나라도 완료되면 통과
await Promise.race([p1, p2]);
// 성공/실패 관계없이 전부 기다림
await Promise.allSettled([p1, p2]);
// 하나라도 성공하면 통과
await Promise.any([p1, p2])

for…of / for…in

for (const item of [1, 2, 3]) { } // 값 순회
for (const key in { a: 1, b: 2 }) { } // 키 순회

최상위 await (Top-level await)

예전에는 await 키워드를 쓰려면 무조건 async 함수 내부에서만 감싸서 사용해야 했습니다. 이제는 모듈 시스템(import/export를 쓰는 환경) 파일의 가장 바깥쪽(Top-level)에서도 async 함수 없이 바로 await를 쓸 수 있습니다.

const response = await fetch('https://api.example.com/config');
export const config = await response.json();

	

R3F Cookbook

R3F는 React 기반에서 three.js를 매우 직관적이고 사용하기 쉬운 컴포넌트 레벨로 개발할 수 있는 패키지입니다. 아래의 영상은 R3F에서 제공하는 컴포넌트, 클래스 등과 R3F를 위한 보다 더 발전된 컴포넌트를 제공하는 drei에 대한 내용을 설명합니다. 이 영상 제작의 목적은 궁극적으로 drei의 컴포넌트를 이해하고 이러한 컴포넌트를 직접 개발하기 위한 지식을 얻기 위함입니다.

위의 영상에 더해 지속적으로 내용이 업데이트되므로 해당 채널에 방문하여 참고하시기 바랍니다.

CSS에서의 광원 효과

DOM 구성은 다음과 같다.

CSS는 다음과 같다.

body {
  background: #bdcddb;
}

.loader {
  position: relative;
  width: 300px;
  height: 300px;
}

.circle {
  position: absolute;
  inset: 35px;
  background-color: #acbaca;
  border-radius: 50%;
  box-shadow: 5px 5px 15px 0 #152b4a66,
    inset 5px 5px 5px rgba(255,255,255,0.55),
    -6px -6px 10px rgba(255,255,255,1);
}

.circle::before {
  content: '';
  position: absolute;
  inset: 4px;
  background: linear-gradient(#2196f3, #e91e63);
  border-radius: 50%;
  animation: animate 2s linear infinite;
}

@keyframes animate {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(350deg);
  }
}

.circle::after {
  content: '';
  position: absolute;
  inset: 35px;
  background: #acbaca;
  border-radius: 50%;
}

.circle span {
  position: absolute;
  inset: 4px;
  background: linear-gradient(#2196f3, #e91e63);
  border-radius: 50%;
  animation: animate 2s linear infinite;
  filter: blur(20px);
  z-index: 1000;
  /* mix-blend-mode: color-dodge; */
  mix-blend-mode: plus-lighter;
}

.circle span::before {
  content: '';
  position: absolute;
  inset: 40px;
  background: #bdcbdb;
  border-radius: 50%;
  animation: animate 2s linear infinite;
  z-index: 1000;
}

결과는 다음과 같다.

CSS의 67번째 줄이 핵심인데, 만약 해당 코드를 제거하면 결과는 다음과 같다.

mix-blend-mode의 값인 plus-lighter는 아직 공식적으로 문서화가 되지 않았고 만약 작동하지 않는다면 color-dodge를 사용하기 바란다. 참고로 color-dodge보다 plus-lighter가 광원의 표현에 더욱 적합하다.