Modern JavaScript

구조를 분해해서 할당 (구조분해할당, 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

// 일부만 추출하거나 나머지 요소들을 배열로 받을 수도 있음
const [one, two, ...rest] = colors;
console.log(one, two, rest); // red green ['blue']

구성 요소를 펼쳐 사용하는 문법 (스프레드 문법, 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 (배열 요소를 개별 인자로 펼쳐서 전달)

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

진짜 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 등이 있음.

R3F Cookbook

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

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

이웃 격자 밖으로 출력된 결과에 대한 자연스러운 처리

  ..
  
  float n = Hash21(id);
  col += Star(gv - vec2(n, fract(n * 34.)) + .5, 1.);
  
  ..

위의 결과를 보면 격자 하나에 대해 어떤 형상 하나가 표시되어 있다. 문제는 형상 하나가 외부 격자 밖에서는 짤려나간다는 것인데, 이를 해결하기 위한 코드가 아래와 같다.

  ..
  
  for(int y=-1; y<=1; y++) {
      for(int x=-1; x<=1; x++) {
        vec2 offs = vec2(x, y);
        
        float n = Hash21(id + offs);
        col += Star(gv - offs - vec2(n, fract(n * 34.)) + .5, 1.);
      }
  }
  
  ..

위의 코드에서 유의해야할 점은 형상 하나가 바로 인접한 이웃의 이웃 밖으로 나갈 경우 처리되지 않는다. 이럴때는 밖으로 나간 것까지 포함되도록 for 문의 반복 범위를 확장해야 한다.

전체 코드는 다음과 같다.

uniform vec3 uResolution;
uniform float uTime;
uniform vec4 uMouse;

mat2 Rot(float a) {
  float s = sin(a);
  float c = cos(a);
  return mat2(c, -s, s, c);
}

float Star(vec2 uv, float flare) {
  float d = length(uv);
  float m = .05 / d;
  
  float rays = max(0., 1. - abs(uv.x * uv.y * 1000.));
  m += rays * flare;
  uv *= Rot(3.1415 / 4.); 
  rays = max(0., 1. - abs(uv.x * uv.y * 1000.));
  m += rays * .3 * flare;

  m *= smoothstep(1., .0, d); // 형상 하나가 바로 인접한 이웃 밖으로 나가지 않도록 해주는 코드

  return m; 
}

float Hash21(vec2 p) {
  p = fract(p * vec2(123.34, 456.21));
  p += dot(p, p + 45.32);
  return fract(p.x * p.y);
}

void main() {
  vec2 uv = (gl_FragCoord.xy - .5 * uResolution.xy) / uResolution.y;
  uv *= 3.;

  vec3 col = vec3(0);

  vec2 gv = fract(uv) - .5;
  vec2 id = floor(uv);

  for(int y=-1; y<=1; y++) {
      for(int x=-1; x<=1; x++) {
        vec2 offs = vec2(x, y);
        
        float n = Hash21(id + offs);
        col += Star(gv - offs - vec2(n, fract(n * 34.)) + .5, 1.);
      }
  }

  if(gv.x > .48 || gv.y > .48) col.r = 1.;  

  gl_FragColor = vec4(col, 1.0);
}

Mandelbrot Fractal

x축은 실수부, y축을 허수로 생각하는 공간(복소수평면)에서의 원점에서 일정한 offset 값만큼 이동하여 제곱한 값에 대한 실수부와 허수를 각각 x, y축으로 삼아 픽셀값으로 시각화한 결과가 Mandelbrot Fractal이며 구현 코드와 그에 대한 결과는 아래와 같다.

uniform vec3 uResolution;
uniform float uTime;
uniform vec4 uMouse;

void main() {
  vec2 uv = (gl_FragCoord.xy - .5 * uResolution.xy) / uResolution.y;
  uv += vec2(0.08, 0.15);
  vec2 c = uv * 2.5 + vec2(-.69955, -.37999); // Offset
  vec2 z = vec2(0.);
  float iter = 0.;
  float max_iter = 60.;

  float h = 2. + sin(uTime);
  for(float i=0.; i<max_iter; i++) {
    z = vec2(
      z.x * z.x - z.y * z.y, // 실수부
      2. * z.x * z.y // 허수부
    ) + c;
    if(length(z) > 2.) break;

    iter++;
  }

  float f = iter / max_iter;
  f = pow(f, .75);
  vec3 col = vec3(f);

  gl_FragColor = vec4(col, 1.0);
}