class A {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
*[ Symbol.iterator ]() {
yield this.x;
yield this.y;
yield this.z;
}
}
const v = new A(1, 2, 3);
console.log(v);
console.log(...v)
console.log([...v])
for (const i of v) {
console.log(i)
}
다중 파일의 드랍에 대한 안전한 처리
웹 API가 계속 발전하고 그에 맞춰 Javascript도 개선되는 과정에서 괴리가 발생하는데요. 그중 한가지 사례로 다중 파일에 대한 드랍처리입니다.
아래의 드랍 처리는 2개 이상의 파일을 드랍했을때 처리되어야할 파일이 누락됩니다.
dropArea.addEventListener('drop', async (e) => {
e.preventDefault();
dropArea.classList.remove('highlight');
fileListElement.innerHTML = '';
const items = e.dataTransfer.items;
if (items) {
for (let i = 0; i < items.length; i++) {
try {
const handle = await items[i].getAsFileSystemHandle();
if (handle.kind === 'file') {
const file = await handle.getFile();
displayFile(file);
} else if (handle.kind === 'directory') {
await traverseFileSystemHandle(handle);
}
} catch (error) {
console.error(error);
}
}
}
});
위 코드의 문제점에 대한 원인은 비동기 처리를 위해 js에서는 for await .. of 문이 도입되었으나 아직 drop 이벤트 객체의 dataTransfer.items 컬렉션은 이를 지원하지 않기 때문입니다. 차선책으로 이를 개선한 코드가 다음과 같습니다.
dropArea.addEventListener('drop', async (e) => {
e.preventDefault();
dropArea.classList.remove('highlight');
fileListElement.innerHTML = '';
const items = e.dataTransfer.items;
if (items) {
const processingPromises = [];
for (let i = 0; i < items.length; i++) {
processingPromises.push((async () => {
if (items[i].getAsFileSystemHandle) {
try {
const handle = await items[i].getAsFileSystemHandle();
if (handle.kind === 'file') {
const file = await handle.getFile();
displayFile(file);
} else if (handle.kind === 'directory') {
await traverseFileSystemHandle(handle);
}
} catch (error) {
console.warn(error);
}
}
})()); // 즉시 실행
}
await Promise.allSettled(processingPromises);
}
});
Compression Streams API를 이용한 텍스트 데이터에 대한 압축 및 해제
Compression Streams API는 웹 표준 API로 데이터에 대한 압축 및 해제를 위한 API이다. 파일 단위가 아닌 데이터에 대한 압축으로 그 목적이 zip과는 다르지만 데이터 압축에 대한 알고리즘은 유사하다. 즉, zip은 여러개의 파일을 압축하면서 압축 해제시에는 파일 명과 디렉토리 구조까지 복원해주지만 Compression Sterams API는 데이터에 대한 압축으로 압축을 해제하면 해제된 데이터가 나오며 그 데이터를 하나의 파일로 저장한다.
Compression Streams API로 압축할 수 있는 데이터는 텍스트 든 바이너리든 모두 가능한데, 아래의 코드는 텍스트에 대한 압축과 압축된 데이터를 다시 원래의 텍스트 데이터로 복원(압축 해제)하는 코드이다.
async function compressAndDecompressString(text) {
// 1. 텍스트를 Uint8Array로 변환
const textEncoder = new TextEncoder();
const encodedData = textEncoder.encode(text);
console.log("원본 데이터:", text);
console.log("원본 데이터 크기 (바이트):", encodedData.byteLength);
// 2. 데이터를 압축 (gzip 형식)
// ReadableStream.from()은 Node.js에서 사용 가능하며,
// 브라우저에서는 ArrayBuffer를 직접 ReadableStream으로 변환해야 함.
// 여기서는 편의를 위해 Blob을 사용하여 ReadableStream을 생성합니다.
const originalStream = new Blob([encodedData]).stream();
const compressedStream = originalStream.pipeThrough(new CompressionStream("gzip"));
// 3. 압축된 데이터를 ArrayBuffer로 읽기
const compressedResponse = new Response(compressedStream);
const compressedBuffer = await compressedResponse.arrayBuffer();
console.log("압축된 데이터 크기 (바이트):", compressedBuffer.byteLength);
console.log("압축률:",
((1 - compressedBuffer.byteLength / encodedData.byteLength) * 100).toFixed(2) + "%");
// 4. 압축된 데이터를 압축 해제
const decompressedStream = new Blob([compressedBuffer]).stream()
.pipeThrough(new DecompressionStream("gzip"));
// 5. 압축 해제된 데이터를 ArrayBuffer로 읽기
const decompressedResponse = new Response(decompressedStream);
const decompressedBuffer = await decompressedResponse.arrayBuffer();
// 6. 압축 해제된 Uint8Array를 다시 텍스트로 변환
const textDecoder = new TextDecoder();
const decompressedText = textDecoder.decode(decompressedBuffer);
console.log("압축 해제된 데이터:", decompressedText);
console.log("압축 해제된 데이터 크기 (바이트):", decompressedBuffer.byteLength);
// 원본 데이터와 압축 해제된 데이터가 동일한지 확인
console.log("원본과 압축 해제된 데이터 일치 여부:", text === decompressedText);
}
// 예제 실행
const longText = `The quick brown fox jumps over the lazy dog.`;
compressAndDecompressString(longText);
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)
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();
zod ?
JavaScript는 타입이 널널한 언어이고 이런 타입 널널한 JavaScript를 보완하고자 태어난 언어가 TypeScript이다. TypeScript는 다른 다양한 언어 중에서도 상당히 복잡한 문법을 갖는 언어이다. 많은 웹 개발자들이 TypeScript를 꺼리는 이유가 바로 이런 복잡함에 있다. 하지만 JavaScript를 사용하면서 JavaScript의 널널한 타입만큼은 좀 어떻게 보완을 할 수 없냐는 갈증을 풀어줄 라이브러리가 zod다.
npm i zod로 딱 설치하고 다음 코드를 고고씽 하면 된다.
import { z } from "zod"
const SexEnum = z.enum(["Man", "Female"])
const User = z.object({
name: z.string(),
age: z.number().optional(),
birthday: z.date(),
sex: SexEnum.nullable(),
live: z.boolean(),
items: z.array(z.string()),
})
const dip2k = User.parse({
name: "dip2k",
// age: 18,
live: true,
// sex: "Man",
sex: null,
birthday: new Date("1999-12-18"),
items: [ "Hammer", "Key" ],
});
console.log(dip2k);
뭔 말이 필요할까. 코드 자체에 모든 설명이 담겨 있다. Hey~ zod?
