Javascript

유용한 자바스크립트 코드 모음집

드리프트2 2024. 2. 17. 19:06

안녕하세요?

개인적으로 자바스크립트 공부할 때 보는 유용한 코드 모음집입니다.

** 목 차 **

문자열을 배열로 분해

const str = 'string'
console.log([...str])
console.log(str.split(''))

실행 결과

[ 's', 't', 'r', 'i', 'n', 'g' ]
[ 's', 't', 'r', 'i', 'n', 'g' ]

문자열의 for...of

for (str of 'string') {
  console.log(str)
}

실행 결과

s
t
r
i
n
g

첫 글자를 대문자로, 나머지를 소문자로 변환

str = 'hELLO'
console.log(str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase())

실행 결과

Hello

indexOf를 사용한 문자열의 출현 횟수 카운트

const str = 'Hello World'
let count = 0
const keyword = 'o'
let position = str.indexOf(keyword)

while (position !== -1) {
  count++
  position = str.indexOf(keyword, position + keyword.length)
}
console.log(`${count}건 히트`)

실행 결과

2건 히트

문자열을 Unicode 정규화

Unicode 정규화를 통해 전각과 반각을 넘어서 검색이 가능해진다.

한국어의 경우 전각과 반각을 구분하는 것이 상대적으로 덜 일반적입니다. 대부분의 한국어 텍스트는 반각 문자로 표현되며, 전각 문자를 사용하는 경우는 드물기 때문입니다.

그래서 일본어의 경우를 예로 들어보겠습니다.

normalize 메소드의 구문

/*
  str: 원래의 문자열
  form: 정규화의 형식 (NFD, NFC, NFKD, NFKC 중 하나)
        인수 생략시 NFC가 기본으로 설정됨
*/
str.normalize(form)
const list = ['アイウエオ']
const type = ['NFD', 'NFC', 'NFKD', 'NFKC']

for (let t of type) {
  for (let l of list) {
    console.log(`${t}: ${l} => ${l.normalize(t)}`)
  }
}

실행 결과

---------- NFD ----------
アイウエオ => アイウエオ
abcABC => abcABC
123 => 123
---------- NFC ----------
アイウエオ => アイウエオ
abcABC => abcABC
123 => 123
---------- NFKD ----------
アイウエオ => アイウエオ
abcABC => abcABC
123 => 123
---------- NFKC ----------
アイウエオ => アイウエオ
abcABC => abcABC
123 => 123

NaN의 비교

'=='나 '===' 연산자로 NaN을 비교하면 올바른 결과를 얻을 수 없으므로, Number 객체의 isNaN 메소드를 사용한다.

// 항상 false가 된다
console.log(NaN === NaN)
// NaN을 올바르게 비교하려면 isNaN 메소드를 사용한다
console.log(Number.isNaN(NaN))

실행 결과

false
true

Number 타입의 포맷

const num = 1234.567
const fmt = new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY',
  currencyDisplay: 'symbol'
})
console.log(fmt.format(num))

실행 결과

¥1,235

min ~ max의 난수

난수 범위가 50 ~ 100

const min = 50
const max = 100
console.log(Math.floor(Math.random() * (max - min + 1)) + min)

실행 결과

55

난수 범위가 0 ~ 100

console.log(Math.floor(Math.random() * 101))

실행 결과

66

배열에서 임의의 요소를 가져오기

const list = [1, 2, 3, 4, 5]
console.log(list[Math.floor(Math.random() * list.length)])

실행 결과

5

월의 마지막 날

console.log(new Date(2024, 2, 0).toLocaleDateString())

실행 결과

2024/2/29

날짜 차이

const dt1 = new Date(2000, 0, 1)
const dt2 = new Date(2000, 0, 15)
const diff = (dt2.getTime() - dt1.getTime()) / (1000 * 60 * 60 * 24)
console.log(`${diff}일의 차이가 있다`)

실행 결과

14일의 차이가 있다

날짜/시간 값을 문자열로 변환

toLocalexxxString() 메소드는 현재 환경의 지역 정보에 따라 최적의 형식으로 날짜/시간을 문자열화한다.

const dt = new Date(2000, 0, 1, 12, 15, 30, 45)

console.log(dt)

console.log(dt.toLocaleString())

console.log(dt.toLocaleDateString())

console.log(dt.toLocaleTimeString())

console.log(dt.toISOString())

console.log(dt.toDateString())

console.log(dt.toJSON())

실행 결과

// 변환 없음
Sat Jan 01 2000 12:15:30 GMT+0900 (한국 표준시)

// toLocaleString
2000/1/1 12:15:30

// toLocaleDateString
2000/1/1

// toLocaleTimeString
12:15:30

// toISOString
2000-01-01T03:15:30.045Z

// toDateString
Sat Jan 01 2000

// toJSON
2000-01-01T03:15:30.045Z

Date 타입의 포맷

const dt = new Date(2000, 0, 1, 12, 15, 30, 45)
const fmt = new Intl.DateTimeFormat('ko-KR', {
  year: 'numeric',
  month: 'short',
  day: '2-digit',
  weekday: 'long',
  hour12: true,
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  dayPeriod: 'short'
})
console.log(fmt.format(dt))

실행 결과

2000년 1월 01일 토요일 오후 12:15:30

레이블 문법의 break

loop:
for (let i = 0; i < 10; i++) {
  if (i === 1) {
    break loop
  }
  console.log(i)
}

실행 결과

0
label2: {
  label1: {
    console.log('label1 block')
    break label2
  }
  console.log('label2 block')
}

실행 결과

label1 block

배열의 인덱스

앞에서 본 경우의 인덱스는 0부터 시작: 0, 1, 2, ...
뒤에서 본 경우의 인덱스는 -1부터 시작: ..., -2, -1

끝에서 요소를 가져오기

const list = [0, 1, 2, 3, 4, 5]

for(let i = -1; i > -list.length; i--) {
  console.log(list.at(i))
} 
5
4
3
2
1

배열의 요소 가져오기

대괄호 문법과 at 메소드 두 가지 패턴이 있습니다.
at 메소드는 음의 인덱스를 지원합니다.

let list = [1, 2, 3, 4, 5]
// 대괄호 문법
console.log(list[0])
// at 메소드
console.log(list.at(-1))

실행 결과

1
5

배열에 여러 요소 추가/교체/삭제

splice 메소드의 구문

/*
  list: 원래의 배열
  start: 시작 위치
  count: 요소 수
  items: 교체 후의 요소 (가변 길이 인수)
*/ 
list.splice(start, count, items)

요소 추가

const list = [0, 1, 2, 3, 4, 5]
console.log(list.splice(3, 0, 'x', 'y', 'z'))
console.log(list)

실행 결과

0,1,2,x,y,z,3,4,5

요소 교체

const list = [0, 1, 2, 3, 4, 5]
console.log(list.splice(3, 2, 'x', 'y', 'z'))
console.log(list)

실행 결과

3,4
0,1,2,x,y,z,5

요소 삭제

const list = [0, 1, 2, 3, 4, 5]
console.log(list.splice(3, 2))
console.log(list)

const list2 = [0, 1, 2, 3, 4, 5]
console.log(list2.splice(3))
console.log(list2)

실행 결과

// list
3,4
0,1,2,5

// list2
3,4,5
0,1,2

배열의 모든 요소 위치 검색

const list = [0, 1, 2, 3, 4, 5, 1]
const keyword = 1
const result = []

list.forEach((value, index) => {
  if (value === keyword) {
    result.push(index)
  }
})
console.log(result)

실행 결과

[ 1, 6 ]

중첩 배열 평탄화

flat 메소드의 구문

/*
  list: 원래의 배열
  depth: 평탄화할 계층 (기본값은 1), 불특정 계층의 경우 Infinity를 지정
*/
list.flat(depth)
const list = [0, 1, 2, 3, 4, 5, [6, 7, [8, 9]], [10]]
console.log(list.flat())
console.log(list.flat(Infinity))

실행 결과

[ 0, 1, 2, 3, 4, 5, 6, 7, [ 8, 9 ], 10 ]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

다차원 배열의 요소 수 얻기

const list = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]
console.log(list.flat().length)

실행 결과

9

배열 내의 요소 이동

배열 내의 지정된 요소를 동일한 배열의 다른 위치로 복사

copyWithin 메소드의 구문

/*
  list: 원래의 배열
  target: 이동 대상 위치
  start: 복사 시작 위치
  end: 복사 종료 위치
*/
copyWithin(target, start, end)
console.log([0, 1, 2, 3, 4, 5].copyWithin(3, 4, 6))
console.log([0, 1, 2, 3, 4, 5].copyWithin(1, 2))
console.log([0, 1, 2, 3, 4, 5].copyWithin(2))
console.log([0, 1, 2, 3, 4, 5].copyWithin(3, -6, -3))

실행 결과

[ 0, 1, 2, 4, 5, 5 ]
[ 0, 2, 3, 4, 5, 5 ]
[ 0, 1, 0, 1, 2, 3 ]
[ 0, 1, 2, 0, 1, 2 ]

배열 같은 객체를 배열로 변환

배열 같은 객체란, 배열처럼 보이지만 배열이 아닌 객체를 말합니다.
Map, Set, HTMLCollection/NoList, arguments, String이 해당됩니다.

from 메소드의 구문

/*
  obj: 배열 같은 객체
  mapFn: 값 변환에 사용하는 함수
  thisArg: 인수 mapFn에서 this가 나타내는 값
*/
Array.from(obj, mapFn, thisArg)

DOM에서 요소 가져오기

<form>
  <select id="select-box">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
  </select>
</form>
const opts = Array.from(document.querySelector('#select-box').options)
opts.forEach((opt) => {
  console.log(opt.value)
})

실행 결과

1
2
3

초기값을 가진 배열 생성

인덱스로 초기화

const list = Array.from(
  { length: 5 },
  (value, index) => {
    return index
  }
)
console.log(list)

실행 결과

[ 0, 1, 2, 3, 4 ]

모든 요소를 고정값으로 초기화

const data = new Array(5)
data.fill('-', 0, data.length)
console.log(data)

실행 결과

[ '-', '-', '-', '-', '-' ]

특정 범위의 연속 번호 생성

const begin = 10
const end = 20
const step = 3

const list = Array.from(
  { length: (end - begin) / step + 1 },
  (value, index) => {
    return begin + (index * step)
  }
)
console.log(list)

실행 결과

[ 10, 13, 16, 19 ]

배열의 얕은 복사

from 메소드

const list = [0, 1, [0, 1, 2]]
const copy = Array.from(list)
copy[2][0] = 100
console.log(list)

실행 결과

[ 0, 1, [ 100, 1, 2 ] ]

스프레드 구문

const list = [0, 1, [0, 1, 2]]
const copy = [...list]
copy[2][0] = 100
console.log(list)

실행 결과

[ 0, 1, [ 100, 1, 2 ] ]

slice 메소드

const list = [0, 1, [0, 1, 2]]
const copy = list.slice()
copy[2][0] = 100
console.log(list)

실행 결과

[ 0, 1, [ 100, 1, 2 ] ]

concat 메소드

const list = [0, 1, [0, 1, 2]]
const copy = list.concat()
copy[2][0] = 100
console.log(list)

실행 결과

[ 0, 1, [ 100, 1, 2 ] ]

배열을 숫자 순으로 정렬

sort 메소드의 구문

/*
  list: 원래의 배열
  m, n: 비교할 요소
  statements: 비교 규칙
*/
list.sort(function(m, n) {
   ...statements...
})

오름차순

const list = [5, 25, 10].sort((m, n) => {
  return m - n
})
console.log(list)

실행 결과

[ 5, 10, 25 ]

내림차순

const list = [5, 25, 10].sort((m, n) => {
  return n - m
})
console.log(list)

실행 결과

[ 25, 10, 5 ]

배열을 임의의 규칙으로 재정렬하기

const levels = ['one', 'two', 'three']
const list = [
  { name: 'name0', level: 'three' },
  { name: 'name1', level: 'two' },
  { name: 'name2', level: 'one' },
  { name: 'name3', level: 'one' },
  { name: 'name4', level: 'three' },
  { name: 'name5', level: 'two' },
]
const result = list.sort((m, n) => {
  return levels.indexOf(m.level) - levels.indexOf(n.level)
})
console.log(result)
실행 결과
[
  { name: 'name2', level: 'one' },
  { name: 'name3', level: 'one' },
  { name: 'name1', level: 'two' },
  { name: 'name5', level: 'two' },
  { name: 'name0', level: 'three' },
  { name: 'name4', level: 'three' }
]

배열을 무작위로 재정렬하기

const list = [0, 1, 2, 3, 4].sort(() => {
  // -0.5 ~ 0.5 범위의 난수
  return Math.random() - 0.5
})
console.log(list)
실행 결과
[ 2, 1, 3, 0, 4 ]

배열의 내용을 순차적으로 처리하기

forEach 메소드의 구문

/*
  list: 원본 배열
  value: 인덱스 값
  index: 원본 배열
  array: 원본 배열
  statements: 요소에 대한 처리, function과 화살표 함수에서 thisArg의 처리가 다름
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
list.forEach(function(value, index, array) {
   ...statements...
}, thisArg)
const obj = { x: 0, y: 1, z: 2 }
const list = ['x', 'y']

list.forEach(function(value) {
  console.log(this[value])
}, obj)

실행 결과

0
1

배열을 지정된 규칙으로 가공하기

map 메소드의 구문

/*
  list: 원본 배열
  value: 요소 값
  index: 인덱스 값
  array: 원본 배열
  statements: 요소에 대한 처리 (반환 값은 가공 후의 값)
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
list.map(function(value, index, array) {
   ...statements...
}, thisArg)
const list = [1, 2, 3]
const result = list.map((value) => {
  return value * 10
})
console.log(result)

실행 결과

[ 10, 20, 30 ]

flatMap (map + flat) 메소드

map과 flat 메소드의 조합 처리보다는 약간이나마 효율적이다.

const list = [1, 2, ,3, null]
const result = list.flatMap((value) => {
  // 평탄화에 의한 공백 열은 제거되므로 제거하고 싶은 요소는 빈 배열을 반환
  if (value === null) {
    return []
  }
  return [value * 10, value * 100]
})
console.log(result)

실행 결과

[ 10, 100, 20, 200, 30, 300 ]

임의의 조건식에 따라 배열을 검색하기

find 메소드의 구문

/*
  list: 원본 배열
  value: 요소 값
  index: 인덱스 값
  array: 원본 배열
  statements: 요소 값을 판정하기 위한 처리 (반환 값은 true/false)
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
list.find(function(value, index, array) {
   ...statements...
}, thisArg)
const list = [
  { name: 'name0', level: 0 },
  { name: 'name1', level: 1 },
  { name: 'name2', level: 2 },
]

let result = list.find((value) => {
  // 판정 결과가 true가 된 요소를 가져온다
  return value.name.includes('0')
})
console.log(result)

// 화살표 함수의 생략 형태
result = list.find(value => value.name.includes('0'))
console.log(result)

실행 결과

{ name: 'name0', level: 0 }
{ name: 'name0', level: 0 }

findIndex 메소드

인덱스 값을 가져오고 싶을 때 사용한다.
구문은 find 메소드와 같다.

const list = [
  { name: 'name0', level: 0 },
  { name: 'name1', level: 1 },
  { name: 'name2', level: 2 },
]
const index = list.findIndex(value => value.name.includes('0'))
console.log(index)

실행 결과

0

조건식에 일치하는 요소가 존재하는지를 판정하기

some 메소드의 구문

일치하는 요소가 1개라도 존재하는 경우에 사용한다.

/*
  list: 원본 배열
  value: 요소 값
  index: 인덱스 값
  array: 원본 배열
  statements: 요소 값을 판정하기 위한 처리 (반환 값은 true/false)
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
list.some(function(value, index, array) {
   ...statements...
}, thisArg)
const list = [
  { name: 'name0', level: 0 },
  { name: null, level: null },
  { name: 'name1', level: 1 },
  { name: 'name2', level: 2 },
]
let result = list.some(value => value.level > 1)
console.log(result)

// 임의의 조건으로 중단
result = list.some((value) => {
  if (value.name === null) {
    return true
  }
  console.log(value)
})
console.log(result)

실행 결과

true
{ name: 'name0', level: 0 }
true

every 메소드의 구문

모든 요소가 일치하는 경우에 사용한다.
구문은 some 메소드와 같다.

const list = [
  { name: 'name0', level: 0 },
  { name: 'name1', level: 1 },
  { name: 'name2', level: 2 },
]
const result = list.every(value => value.level >= 0)
console.log(result)

실행 결과

true

배열에서 조건에 맞는 요소만 가져오기

filter 메소드의 구문

/*
  list: 원래의 배열
  value: 요소 값
  index: 인덱스 값
  array: 원래의 배열
  statements: 요소 값을 판단하기 위한 처리 (반환 값은 true/false)
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
list.filter(fuction(value, index, array) {
   ...statements...
}, thisArg)
const list = [
  { name: 'name0', level: 0 },
  { name: 'name1', level: 1 },
  { name: 'name2', level: 2 },
]
const result = list.filter(value => value.level > 0)
console.log(result)

실행 결과

[ { name: 'name1', level: 1 }, { name: 'name2', level: 2 } ]

배열의 요소를 순서대로 처리해서 하나로 묶기

reduce 메소드의 구문

/*
  list: 원래의 배열
  result: 이전까지의 결과
  value: 요소 값
  index: 인덱스 값
  array: 원래의 배열
  statements: 요소 값에 대한 처리
  initial: 초기 값, 첫 번째 루프에서 result에 전달하는 값
*/
list.reduce(fuction(result, value, index, array) {
   ...statements...
}, initial)
const list = [0, 1, 2]
// result 인수에 초기 값을 설정하는 경우: 초기 값으로 100을 설정
let result = list.reduce((result, value) => {
  return result + value
}, 100)
console.log(result)

// result 인수에 초기 값을 설정하지 않는 경우: 초기 값으로 배열의 0번째 값이 설정됨
result = list.reduce((result, value) => result + value)
console.log(result)

실행 결과

103
3

reduceRight의 구문

reduce 메소드가 왼쪽에서 오른쪽 방향으로 연산하는 반면, reduceRight 메소드는 오른쪽에서 왼쪽으로 연산한다.
구문은 reduce 메소드와 같다.

const list = [
  [0, 1],
  [2, 3],
  [4, 5],
]
let result = list.reduce((result, value) => result.concat(value))
console.log(result)

result = list.reduceRight((result, value) => result.concat(value))
console.log(result)

실행 결과

// reduce
[ 0, 1, 2, 3, 4, 5 ]

// reduceRight
[ 4, 5, 2, 3, 0, 1 ]

for문에서의 분해 할당

리스트

const list = [['x', 0], ['y', 1], ['z', 2]]

for (const [value1, value2] of list) {
  console.log(value1, value2)
}

실행 결과

x 0
y 1
z 2

객체

const map = new Map([
  ['x', 0],
  ['y', 1],
  ['z', 2]
])

// for...of의 map은 map.entries()의 생략
for (const [key, value] of map) {
  console.log(key, value)
}

실행 결과

x 0
y 1
z 2

const obj = { x: 0, y: 1, z: 2 }

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
}

실행 결과

x 0
y 1
z 2

Map의 메소드 체인

const map = new Map()
console.log(map.set('x', 0).set('y', 1).set('z', 2))

실행 결과

Map(3) { 'x' => 0, 'y' => 1, 'z' => 2 }

Map의 가져오기 키가 존재하지 않는 경우의 기본 값 처리

const map = new Map([
  ['x', 0],
  ['y', 1],
  ['z', 2],
])
console.log(map.has('xxx') ? map.get('xxx') : 'xxx')

실행 결과

xxx

Map의 안티 패턴

map에 대해 괄호 구문으로 값을 저장하지 않는다.
괄호 구문으로 값을 설정한 경우는, get 메소드로 값을 가져올 수 없다.

const map = new Map()
map['x'] = 0
console.log(map['x'])
// 괄호 구문으로 값을 저장하면 get 메소드를 사용하여 값을 가져올 수 없다
console.log(map.get('x'))

실행 결과

0
undefined

Map의 배열화

const map = new Map([
  ['x', 0],
  ['y', 1],
  ['z', 2],
])
// 키를 배열화
console.log(Array.from(map.keys()))
// 값을 배열화
console.log(Array.from(map.values()))
// 키/값을 배열화
console.log(Array.from(map.entries()))

실행 결과

[ 'x', 'y', 'z' ]
[ 0, 1, 2 ]
[ [ 'x', 0 ], [ 'y', 1 ], [ 'z', 2 ] ]

Map의 내용을 순서대로 처리

forEach 메소드의 구문

/*
  dic: 원래의 맵
  value: 요소 값
  key: 키 값
  map: 원래의 맵
  statements: 요소 값에 대한 처리
  thisArg: 콜백 함수에서 this가 나타내는 값
*/
dic.forEach(fuction(value, key, map)) {
   ...statements...
}, thisArag)
const map = new Map([
  ['x', 0],
  ['y', 1],
  ['z', 2],
])
map.forEach((value, key) => {
  console.log(`${key}: ${value}`)
})

실행 결과

x: 0
y: 1
z: 2

Object와 Map의 상호 변환

Object를 Map으로 변환

const map = new Map([
  ['x', 0],
  ['y', 1],
  ['z', 2],
])
const obj = Object.fromEntries(map)
console.log(obj)

실행 결과

{ x: 0, y: 1, z: 2 }

Map을 객체로 변환

const obj = {
  x: 0,
  y: 1,
  z: 2,
}
const map = new Map(Object.entries(obj))
console.log(map)

실행 결과

Map(3) { 'x' => 0, 'y' => 1, 'z' => 2 }

WeakMap - 약한 참조 키의 Map

약한 참조란, Map 외에서 키가 참조되지 않게 되면, 가비지 컬렉션의 대상이 된다는 것이다.
WeakMap에서는 키(객체)가 파괴되면 함께 값도 파괴되므로, 메모리 누수를 해결할 수 있다.

WeakMap의 제한

  • 키는 참조형이어야 한다
  • get, set, has, delete 메소드만 사용할 수 있다

사용 용도

주요 용도로는, 객체의 부수적인 데이터를 관리하기 위해 사용한다. 예를 들어, 객체에 대한 접근 횟수를 감시하는 등.

Map에서 키(객체)를 삭제

let obj = {}
const map = new Map()
map.set(obj, 'object')
// 객체를 파괴
obj = null
// Map에 저장한 객체는 파괴되지 않고, 살아남는다
// 사라져야 할 객체가 사라지지 않아서 메모리 누수의 원인이 된다
console.log(map.size)

실행 결과

1

WeakMap에서 키(객체)를 삭제

let obj = {}
const map = new WeakMap()
map.set(obj, 'object')
// 객체를 파괴
obj = null
// WeakMap에서도 키가 파괴된다
console.log(map.size)

실행 결과

undefined

WeakMap을 활용한 클래스

class WeakMapClass {
  // 초기화
  constructor(init) {
    this._weakMap = new WeakMap(init)
  }
   // 존재 체크
  has(key) {
    return this._weakMap.has(key)
  }
  // 저장
  set(key, value) {
    this._weakMap.set(key, value)
    return this
  }
  // 가져오기
  get(key) {
    return this._weakMap.get(k)
  }
  // 삭제
  delete(key) {
    return this._weakMap.delete(key)
  }
  // 전체 삭제
  clear() {
    this._weakMap = new WeakMap()
  }
}

배열의 중복 삭제

indexOf와 filter 메소드

배열 내에서 가장 처음 일치하는지 여부로 판단하여, 중복을 제거한다.

const list = [0, 1, 2, 0, 1, 2, 3]
const result = list.filter((value, index) => {
  return list.indexOf(value) === index
})
console.log(result)

실행 결과

[ 0, 1, 2, 3 ]

Set

const set = new Set([0, 1, 2, 0, 1, 2, 3])
// from 메소드로 배열화
console.log(Array.from(set))
// 스프레드 구문으로 배열화
console.log([...set])

실행 결과

[ 0, 1, 2, 3 ]
[ 0, 1, 2, 3 ]

Set의 메소드 체인

const set = new Set()
set.add(0).add(1).add(2).add(0)
console.log(set)

실행 결과

Set(3) { 0, 1, 2 }

Object 리터럴을 문자열로 변환

console.log(`{"x":0,"y":1,"z":2}`)

실행 결과

{"x":0,"y":1,"z":2}

Object와 JSON 문자열의 상호 변환

객체를 JSON 문자열로 변환

const obj = {
  x: 0,
  y: 1,
  z: 2,
}
console.log(JSON.stringify(obj))

실행 결과

{"x":0,"y":1,"z":2}

JSON 문자열을 객체로 변환

const json = `{"x":0,"y":1,"z":2}`
console.log(JSON.parse(json))

실행 결과

{ x: 0, y: 1, z: 2 }

JSON 문자열을 객체로 변환 - Date형 복원

const obj = {
  str: 'string',
  date: new Date()
}
// obj.date는 문자열로 복원된다
const jsonString = JSON.stringify(obj)
console.log(jsonString)

// jsonString.date를 Date형으로 변환
const parsedObj = JSON.parse(jsonString, (key, value) => {
  if (typeof(value) === 'string' &&
      value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/)){
    return new Date(value)
  }
  return value
})
console.log(parsedObj)

실행 결과

{"str":"string","date":"2024-02-05T14:41:29.981Z"}
{ str: 'string', date: 2024-02-05T14:41:29.981Z }

eval 함수

eval 함수는 주어진 문자열을 코드로 평가하여 실행한다.
eval 함수를 사용하기보다는 다른 방법을 사용할 수 없는지 검토하는 것이 좋다. 대부분, 대안책은 준비되어 있다.
다음의 이유로 남용은 피하는 것이 좋다.

  • 사용자의 입력 내용을 eval에 주면, 임의의 스크립트를 자유롭게 실행할 수 있게 되는 가능성이 있다
  • 일반적인 코드보다는 성능이 나쁘고, 처리 속도가 느리다
  • eval('console.log("eval 함수")')
    

let data = 'data'
// { var obj = data } 와 같은 의미
eval(var obj = ${data})
console.log(obj)

// 객체의 접근 프로퍼티의 전환
const obj = { x: 0, y: 1, z: 2 }
const prop = 'x'
eval(console.log(obj.${prop}))


실행 결과
```javascript
eval 함수
data
0

RegExp 객체를 생성

RegExp 객체의 생성은 다음의 2패턴이 있다.

  • RegExp 객체의 생성자를 이용한다
  • 정규 표현 리터럴을 이용한다
/*
  patter: 정규 표현 패턴
  opts: 동작 옵션
*/
new RegExp(pattern, opts) // 생성자
/pattern/opts // 리터럴
옵션 개요
d 매치한 위치를 기록한다
g 글로벌(전체)에 대해 매치한다
i 대문자/소문자를 구분한다
m 여러 줄에 대응한다
s '.'가 개행 문자에 일치하도록 한다
u Unicode에 대응한다, 서로게이트 페어 대책 등에서 사용한다
y lastIndex 프로퍼티로 지정한 위치에서 매치한다

RegExp 생성자로 정규 표현 객체를 받는다

RegExp의 생성자는 정규 표현 리터럴을 받을 수 있다.

console.log(new RegExp(/[0-9]/, 'i'))
/[0-9]/i

문자열이 정규 표현 패턴에 매치했는지 판정

const pattern = /^[0-9]{3}-[0-9]{4}$/

const str1 = '012-3456'
console.log(pattern.test(str1))

const str2 = '0123-4567'
console.log(pattern.test(str2))

실행 결과

true
false

정규 표현 패턴에 매치한 문자열을 가져오기

match 메소드는 글로벌(전체) 매치의 유효/무효에 따라 결과가 달라진다.

|글로벌(전체) 매치| 결과| 서브매치 문자열|
|-|-|
|유효| 매치한 모든 문자열 |포함하지 않는다|
|무효| 매치한 첫 번째 문자열 |포함한다|

const str = `zip code1: 012-3456
             zip code2: 123-4567`
const pattern1 = /[0-9]{3}-[0-9]{4}/g
for (const result of str.match(pattern1)) {
  console.log(result)
}

const pattern2 = /([0-9]{3})-([0-9]{4})/
console.log(str.match(pattern2))

실행 결과

// 글로벌(전체) 매치 유효, 서브매치 없음
012-3456
123-4567

// 글로벌(전체) 매치 무효, 서브매치 있음
[
  '012-3456',
  '012',
  '3456',
  index: 11,
  input: 'zip code1: 012-3456\n             zip code2: 123-4567',
  groups: undefined
]

서브매치의 그룹에 이름을 붙이기

const str = `zip code1: 012-3456
             zip code2: 123-4567`
const pattern = /(?<city>[0-9]{3})-(?<local>[0-9]{4})/
const result = str.match(pattern)
console.log(result.groups.city, result.groups.local)

실행 결과

012 3456

정규 표현의 매치 결과를 모아서 가져오기

matchAll 메소드는 글로벌(전체) 매치하는 모든 문자열과 서브매치 문자열 등을 결과로 반환한다.

const str = `zip code1: 012-3456
             zip code2: 123-4567`
const pattern = /([a-z].*)([0-9]{3}-[0-9]{4})/g

for (const result of str.matchAll(pattern)) {
  console.log(result)
}

실행 결과

[
  'zip code1: 012-3456',
  'zip code1: ',
  '012-3456',
  index: 0,
  input: 'zip code1: 012-3456\n             zip code2: 123-4567',
  groups: undefined
]
[
  'zip code2: 123-4567',
  'zip code2: ',
  '123-4567',
  index: 33,
  input: 'zip code1: 012-3456\n             zip code2: 123-4567',
  groups: undefined
]

레거시 환경

matchAll 메소드는 ES2020에서 추가된 메소드이므로, IE 같은 레거시 브라우저에서는 사용할 수 없다.
레거시 환경에서 matchAll 메소드와 같은 동작을 구현한 경우는 exec 메소드를 사용한다.
exec 메소드는, 다음의 특징을 가진다.

  • 글로벌(전체) 검색과 관계없이, 실행 결과는 항상 1개
  • 서브매치 문자열, 확장 프로퍼티 등의 정보를 포함한다
  • 마지막에 매치한 문자열의 위치를 기록한다 (다음에는 이전의 문자열 위치에서 검색을 재개한다)
  • 매칭 문자열이 존재하지 않는 경우는 반환 값을 null
const str = `zip code1: 012-3456
             zip code2: 123-4567`
const pattern = /[0-9]{3}-[0-9]{4}/g

while ((result = pattern.exec(str)) !== null) {
  console.log(result)
}

실행 결과

[
  '012-3456',
  index: 11,
  input: 'zip code1: 012-3456\n             zip code2: 123-4567',
  groups: undefined
]
[
  '123-4567',
  index: 44,
  input: 'zip code1: 012-3456\n             zip code2: 123-4567',
  groups: undefined
]

정규 표현으로 문자열을 바꾸기

replace 메소드의 구문

/*
  str: 바꿀 대상 문자열
  pattern: 정규 표현
  rep: 바꾼 후의 문자열
  match: 매치한 문자열
  p1, p2, p3...: 서브매치 문자열 (그룹의 수에 따라 변동)
  offset: 매치한 문자열의 위치
  string: 검색 대상의 문자열
*/
str.replace(pattern, rep, fuction(match, p1, p2, p3..., offset, string)) {
   ...statements...
}
변수 개요
$& 매치한 부분 문자열
$` 매치한 부분 문자열의 바로 앞의 문자열
$' 매치한 부분 문자열의 바로 뒤의 문자열
$1 〜 100 서브매치 문자열
$$ 달러 기호
const str = 'tel number: 00-111-2222'
const pattern = /(\d{1,2})-(\d{2,4})-(\d{3,4})/g
console.log(str.replace(pattern, '$1($2)$3'))
// 정규 표현에서 g 옵션을 지정하지 않은 경우는 매치한 첫 번째 문자열만을 바꾼다
console.log(str.replace(/\d/, 'x'))

실행 결과

tel number: 00(111)2222
tel number: x0-111-2222

replcaeAll 메소드

모든 매칭 문자열을 바꾸려면, replcae 메소드 대신에 replaceAll 메소드를 사용할 수도 있다.
replaceAll 메소드를 사용하면 다음의 장점이 있다.

  • 모든 매칭 문자열을 바꾸는 것이 의도가 명확해진다
  • 글로벌(전체) 매치하기 위한 g 옵션이 지정되어 있지 않은 경우는, 에러가 된다
const str = 'tel number: 00-111-2222'
const pattern = /(\d{1,2})-(\d{2,4})-(\d{3,4})/g
console.log(str.replaceAll(pattern, '$1($2)$3'))
console.log(str.replaceAll(/\d/g, 'x'))

실행 결과

tel number: 00(111)2222
tel number: xx-xxx-xxxx

콜백 함수를 이용한 바꾸기

const str = 'tel number: 00-111-2222'
const pattern = /\D/g
const result = str.replace(pattern, (match) => {
  return match.toUpperCase()
})
console.log(result)

실행 결과

TEL NUMBER: 00-111-2222

정규 표현으로 문자열을 분할

const pattern = /[\/\./-]/g
console.log('1970-1-1'.split(pattern))
console.log('1970/1/1'.split(pattern))
console.log('1970.1.1'.split(pattern))

실행 결과

[ '1970', '1', '1' ]
[ '1970', '1', '1' ]
[ '1970', '1', '1' ]

정규 표현 - 후방 참조

후방 참조는, 같은 정규 표현에서 이전에 캡처한 것을 참조하는 것이다.

const str = 'tel number: 00-00-00'
// \1: 캡처 그룹의 번호
const pattern1 = /(\d{1,2})-\1-\1/g
// 이름 붙은 캡처 그룹
const pattern2 = /(?<num>\d{1,2})-\k<num>-\k<num>/g
console.log(str.match(pattern1))
console.log(str.match(pattern2))

실행 결과

[ '00-00-00' ]
[ '00-00-00' ]

정규 표현 - 캡처 그룹을 무효화

(?:...) 로 매치 패턴을 둘러싸는 것으로, 그룹을 서브매치의 대상에서 제외할 수 있다.

const str = 'xyz0123'
// 캡처 있는 그룹핑
const pattern1 = /^([a-z0-9]{3})(\d{1,4})$/
// 캡처 없는 그룹핑
const pattern2 = /^(?:[a-z0-9]{3})(\d{1,4})$/
console.log(str.match(pattern1))
console.log(str.match(pattern2))

실행 결과

// 캡처 있는 그룹핑
[
  'xyz0123',
  'xyz',
  '0123',
  index: 0,
  input: 'xyz0123',
  groups: undefined
]

// 캡처 없는 그룹핑
[ 
  'xyz0123',
  '0123',
  index: 0,
  input: 'xyz0123',
  groups: undefined
]

정규 표현 - 앞뒤의 문자열의 유무에 따른 매치 판정

정규 표현의 선행, 후행은 앞뒤의 문자열이 존재하는지 여부로 문자열을 매치할지 판정하기 위한 표현이다.
선행, 후행은 다음의 4종류가 있다.

정규 표현 개요
X(?=Y) 긍정 선행: X의 바로 뒤에 Y가 이어지는 경우는 X에 매치한다
X(?!Y) 부정 선행: X의 바로 뒤에 Y가 이어지지 않는 경우는 X에 매치한다
(?<=Y)X 긍정 후행: X의 바로 앞에 Y가 있는 경우는 X에 매치한다
(?<!Y)X 부정 후행: X의 바로 앞에 Y가 없는 경우는 X에 매치한다
// 긍정 선행
console.log(str.match(/xyz(?=0)/g))
// 부정 선행
console.log(str.match(/xyz(?!3)/g))
// 부정 선행
console.log(str.match(/(?<=z)0123/g))
// 부정 후행
console.log(str.match(/(?<!x)0123/g))

실행 결과

// 긍정 선행
[ 'xyz' ]

// 부정 선행
[ 'xyz' ]

// 부정 선행
[ '0123' ]

// 부정 후행
[ '0123' ]

Unicode 프로퍼티로 특정 문자열을 가져오기

Unicode 프로퍼티란 Unicode에서 정의한 문자에 대해 할당된 속성(프로퍼티)의 것이다.
정규 표현 패턴의 안에서 Unicode 프로퍼티를 사용할 수 있도록 한 것이 Unicode 프로퍼티 이스케이프라는 구문이다.
정규 표현 패턴에서 다음과 같이 사용한다.

// 'sc='는 프로퍼티의 분류를 의미한다
// 'gc='를 생략할 수 있는데 생략한 경우는 다음과 같다 'gc=Letter' -> 'Letter'
/[\p{sc=Han}]+/gu

다음과 같은 프로퍼티가 있다.

프로퍼티 개요
\p{Letter}, \p{L} 문자
\p{Punctuation}, \p{P} 구두점
\p{Uppercase_Letter}, \p{Lu} 영대문자 (전각, 반각)
\p{Lowercase_Letter}, \p{Ll} 영소문자 (전각, 반각)
\p{Number}, \p{N} 반각 / 전각 숫자 (로마 숫자도 포함)
\p{Nd} 반각 / 전각 숫자 (10진수)
\p{Space_Separator}, \p{Zs} 공백
\p{sc=Hangul}+/gu 한글
\p{sc=Hiragana}, \p{sc=Hira} 히라가나
\p{sc=Katakana}, \p{sc=Kana} 카타카나
\p{sc=Han} 한자
const str = 'STRINGもじれつ文字列한글'
// 영대문자
console.log(str.match(/[\p{Lu}]+/gu))
// 한글
console.log(str.match(/[\p{sc=Hangul}]+/gu))
// 히라가나
console.log(str.match(/[\p{sc=Hira}]+/gu))
// 한자
console.log(str.match(/[\p{sc=Han}]+/gu))

실행 결과

[ 'STRING' ]
[ '한글' ]
[ 'もじれつ' ]
[ '文字列' ]

유니코드 한글 프로퍼티

초성은 Hangul_Syllable_Type=Leading_Jamo 또는 HST=L
중성은 Hangul_Syllable_Type=Vowel_Jamo 또는 HST=V
종성은 Hangul_Syllable_Type=Trailing_Jamo 또는 HST=T
그리고 완성된 한글 음절은 Hangul_Syllable_Type=LV_Syllable 또는 HST=LV로 표현할 수 있습니다.

// 초성만 있는 문자열 /[\p{HST=L}]+/gu

// 중성만 있는 문자열 /[\p{HST=V}]+/gu

// 종성만 있는 문자열 /[\p{HST=T}]+/gu

// 완성된 한글 음절만 있는 문자열 /[\p{HST=LV}]+/gu

// 한글 자모나 음절을 포함하는 문자열 /[\p{Script=Hangul}]+/gu

끝.