- Rethinking the JavaScript ternary operator1
- Break a forEach Loop with JavaScript2
- You Can Label a JavaScript
ifStatement | CSS-Tricks3 - Refactoring optional chaining into a large codebase: lessons learned – Lea Verou4
- [HTML5] 꼼꼼히 살펴보는 SCRIPT 엘리먼트 - 코드쓰는사람5
- Single Page Applications using Rust6
- Fast and maintainable patterns for fetching from a database – Sophie Alpert7
- Deep-copying in JavaScript using structuredClone8
- Holistic Review of TC39 “Dataflow” Proposals — Tab Completion9
Footnotes
-
삼항연산자(ternary)의 어두운면과 우려를 나타내는 글 ↩
-
forEach루프break하는 트릭. 참조하는 배열의 length값을 0으로. ↩ -
조건문에서
label을 지정해서 해당 블록으로break가 가능하다는걸 보여주는 내용. 실제 적용할만한 사례는 거의 없고 글쓴이는 while+switch문에서 break를 좀 더 효율적으로 사용한 예를 보여주고 있다. ↩ -
optional chaining 문법으로 리팩토링 하면서…변경 가능한 부분(삼항연산자, 배열, 기능탐지)과 주의사항(값 할당, 잘못된 위치에 표기), 조심할 부분(null/undefined, 연산 순서와 순위, return 항상 호출됨)으로 구분해서 정리한 글. 커밋을 보면 실제로 어떻게 작업했는지 확인도 가능하다. ↩
-
<script />태그와 속성값에 대한 정리 ↩ -
rust로 작성하고 wasm으로 컴파일해서 클라이언트 프로그램을 작성하는 방법을 자세하게 설명하고 있다. rust에 관심이 있거나 하다면 볼만한 글. ↩
-
종속성 구조를 파악해서 최적의 병렬화로 빠르게 데이터 가져오기 ↩
-
structuredClone()↩ -
pipe, flow 제안(초안)사항을 검토하는 관점에서 보는 시각 ↩
- Temporal documentation1
- Is It Time for the JavaScript Temporal API?2
- JS Dates Are About to Be Fixed | TimeTime
- Using Intl.RelativeTimeFormat for Localized Relative Timings
- Everything You Need to Know About Date in JavaScript | CSS-Tricks3
- 자바스크립트에서 타임존 다루기 (1) : NHN Cloud Meetup4
- 자바스크립트에서 타임존 다루기 (2) : NHN Cloud Meetup
Footnotes
import isAfter from 'date-fns/isAfter';
isAfter(new Date(), new Date(DATE))
날짜 비교 할 일이 있어서 별 생각 없이 new Date를 때렸는데 safari에서 안되는 문제가 발견되었다. 콘솔을 확인해보니 yyyy-MM-dd HH:mm:ss 해당 형태의 포멧 에서는 안된다. 평소에 new Date 보다는 moment나 date-fns같은 라이브러리를 당연하게 써오다 보니 몰랐다. 그런데 또 다른 생각을 해보자면 저런 문제가 있기 때문에 더 적극적으로 라이브러리를 사용해야 한다는 게 함정.
import isAfter from 'date-fns/isAfter';
import format from 'date-fns/format';
isAfter(new Date(), format(DATE))
const obj = {
a: 1,
b: 2,
}
console.log(obj[['a']]) // 1
console.log(obj[['b']]) // 2
이게 되네 🤔
Immer가 생성하는 맵과 세트는 인위적으로 불변으로 만들어집니다. 즉, 프로듀서 외부에서 세트, 클리어 등과 같은 변경 메서드를 시도할 때 예외(throw an exception)가 발생합니다.
test('Map and Set', () => {
const baseMap = new Map();
const nextBaseMap = create(baseMap, (draft) => {
draft.set('a', 1);
});
expect(nextBaseMap).toMatchInlineSnapshot(`
Map {
"a" => 1,
}
`);
});
import { match } from 'ts-pattern'
type Format = 'webp' | 'jpg'
type Params = {
id: string
quality: keyof typeof QUALITY_MAP
format: Format
}
const QUALITY_MAP = {
player_background: '0',
video_frames_start: '1',
video_frames_middle: '2',
video_frames_end: '3',
lowest_quality: 'default',
medium_quality: 'mqdefault',
high_quality: 'hqdefault',
standard_quality: 'sddefault',
unscaled_resolution: 'maxresdefault',
}
const BASE_URL = 'https://i.ytimg.com'
const VI = (format: Format) =>
match(format)
.with('jpg', () => 'vi')
.otherwise(() => ['vi', format].join('_'))
export function getThumbnail({ id, quality, format }: Params) {
return [BASE_URL, VI(format), id, QUALITY_MAP[quality]]
.join('/')
.concat(`.${format}`)
}
Map으로 localStorage 래핑
class LocalStorageMap {
constructor(storageKey) {
this.storageKey = storageKey
this.map = this.loadFromStorage()
}
loadFromStorage() {
const data = localStorage.getItem(this.storageKey)
return data ? new Map(JSON.parse(data)) : new Map()
}
saveToStorage() {
localStorage.setItem(this.storageKey, JSON.stringify([...this.map]))
}
set(key, value) {
this.map.set(key, value)
this.saveToStorage()
}
get(key) {
return this.map.get(key)
}
delete(key) {
const result = this.map.delete(key)
this.saveToStorage()
return result
}
clear() {
this.map.clear()
localStorage.removeItem(this.storageKey)
}
}
CAUTION
- localStorage는 문자열만 저장 → JSON 직렬화 필수
- 용량 제한 5~10MB 주의
전화번호 포맷팅
function formatPhoneNumber(phoneNumber) {
const cleaned = phoneNumber.replace(/\D/g, '')
if (cleaned.length === 11) {
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')
} else if (cleaned.length === 10 && cleaned.startsWith('02')) {
return cleaned.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3')
} else if (cleaned.length === 10) {
return cleaned.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3')
} else if (cleaned.length === 9 && cleaned.startsWith('02')) {
return cleaned.replace(/(\d{2})(\d{3})(\d{3})/, '$1-$2-$3')
}
return 'Invalid phone number'
}
formatPhoneNumber('01012341234') // 010-1234-1234
formatPhoneNumber('021234567') // 02-123-4567 거리 만km 포맷팅
function formatToManKm(distance: number): string {
const manKm = (distance / 10000).toFixed(1)
return `${manKm}만km`
}
formatToManKm(104335) // "10.4만km" 특수 기호 제거
function removeSpecialCharacters(input) {
return input.replace(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/g, '')
}
removeSpecialCharacters('Hello_World123! 안녕하세요?')
// "HelloWorld123 안녕하세요" JavaScript delete 연산자
const obj = { name: 'Alice', age: 25 }
delete obj.age // true
// 존재하지 않는 속성 삭제도 true
delete obj.city // true
// configurable: false는 삭제 불가
const locked = Object.defineProperty({}, 'readOnly', {
value: 'I cannot be deleted',
configurable: false,
})
delete locked.readOnly // false
// 전역 변수 삭제 불가
let globalVar = 'exists'
delete globalVar // false
// 배열 요소 삭제 (hole 생성)
let arr = [1, 2, 3]
delete arr[1] // [1, <empty>, 3]
- 설정 가능한(configurable) 속성에만 사용
- 배열은
splice권장
DFS (깊이 우선 탐색)
// 재귀 방식
function dfs(graph, node, visited = new Set()) {
visited.add(node)
console.log(node)
graph[node].forEach((neighbor) => {
if (!visited.has(neighbor)) dfs(graph, neighbor, visited)
})
}
// 스택 방식
function dfsStack(graph, startNode) {
const stack = [startNode]
const visited = new Set()
while (stack.length > 0) {
const node = stack.pop()
if (!visited.has(node)) {
console.log(node)
visited.add(node)
graph[node]
.slice()
.reverse()
.forEach((neighbor) => {
if (!visited.has(neighbor)) stack.push(neighbor)
})
}
}
}
const graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2],
}
dfs(graph, 0) // 0, 1, 3, 4, 2, 5 - 캔버스로 동영상 프레임을 캡쳐한다.
- 텍스트를 입력 받는다. 해당 텍스트를 서버에 보내고 음성파일을 응답받는다.
Blob데이터는URL.createObjectURL()로 변환해서img,audio태그에 연결한다.
JavaScript 부동소수점 오차 - 0.1 + 0.2 = 0.30000000000000004
IEEE 754 64비트 부동소수점 표준. 0.1은 이진법으로 무한 반복(0.0001100110011...)이라 근사치 저장됨.
// 해결책
;(1.1 * 10 + 0.1 * 10) / 10 // 정수 변환
parseFloat((1.1 + 0.1).toFixed(1)) // toFixed
Math.abs(1.1 + 0.1 - 1.2) < Number.EPSILON // 비교 시
// 정밀 계산: decimal.js, big.js, bignumber.js
JS만의 문제 아님. Python, Java, C++ 등 IEEE 754 사용하는 모든 언어에서 동일.
Date.getDay() - 요일 반환 (0=일요일, 6=토요일)
new Date().getDay() // 0~6
new Date('2024-12-25').getDay() // 3 (수요일)
new Date(2024, 11, 25).getDay() // 월은 0부터 시작
const days = ['일', '월', '화', '수', '목', '금', '토']
days[new Date().getDay()] // 오늘 요일 현재 시간부터 목표 시간까지 남은 시간 계산
function calculateRemainingTime(targetTime) {
const now = new Date()
const [h, m, s] = targetTime.split(':').map(Number)
const target = new Date()
target.setHours(h, m, s)
const diff = target - now
if (diff < 0) return null
return {
hours: Math.floor(diff / 3600000) % 24,
minutes: Math.floor(diff / 60000) % 60,
seconds: Math.floor(diff / 1000) % 60,
}
}
calculateRemainingTime('18:30:00')
// { hours: 2, minutes: 15, seconds: 30 } 두 요소 스크롤 동기화
const box1 = document.getElementById('box1')
const box2 = document.getElementById('box2')
let isSyncing = false
function syncScroll(source, target, sourceWidth, targetWidth) {
const ratio = source.scrollLeft / (source.scrollWidth - sourceWidth)
target.scrollLeft = ratio * (target.scrollWidth - targetWidth)
}
box1.addEventListener('scroll', () => {
if (!isSyncing) {
isSyncing = true
syncScroll(box1, box2, 1280, 640)
isSyncing = false
}
})
box2.addEventListener('scroll', () => {
if (!isSyncing) {
isSyncing = true
syncScroll(box2, box1, 640, 1280)
isSyncing = false
}
})
isSyncing 플래그로 무한 이벤트 루프 방지
콜백 헬 → Promise → async/await 진화
// 콜백 헬
fetchData(() => {
processData(() => {
displayData()
})
})
// Promise 체인
fetchData().then(processData).then(displayData)
// async/await
async function main() {
const data = await fetchData()
const processed = await processData(data)
await displayData(processed)
}
Promise는 모나드처럼 동작 - then이 bind/flatMap 역할
- map: 값 변환 (중첩 허용)
- flatMap: 값 변환 + 평탄화 (Promise의 then)
- How to Modify Nodes in an Abstract Syntax Tree | CSS-Tricks1
- AST for JavaScript developers. TL;DR This article is my talk for… | by Bohdan Liashenko | ITNEXT2
- GitHub - NV/CSSOM: Unmaintained! ⚠️ CSS Object Model implemented in pure JavaScript. Also, a CSS parser.3
- GitHub - csstree/csstree: A tool set for CSS including fast detailed parser, walker, generator and lexer based on W3C specs and browser implementations
Footnotes
Designing a JavaScript Plugin System | CSS-Tricks1
Footnotes
-
플러그인은 라이브러리와 프레임워크의 공통 기능이며 개발자가 안전하고 확장 가능한 방식으로 기능을 추가할 수 있도록 한다. 그래서 추가 유지 관리 부담이 없다. ↩
Quick tip: reusable Array search predicates - JASON Format
arr.filter(callback(element[, index[, array]])[, thisArg])
배열 메서드에서 2번째 인자 thisArg에 참조값을 전달해서 재사용 가능한 함수를 만드는 트릭. 단 성능 이슈가 있으므로 주의해야 한다.