JS - 연산자(2)

hoin_lee·2023년 10월 2일
1

TIL

목록 보기
223/234

구조 분해 할당 (destructuring)/확장(spread)연산자

구조분해 할당과 스프레드 연산자는 프론트 개발자라면 꼭 알고 가야하는 연산자 중 하나로 특히 React에서 많은 활용을 한다.
props를 내려줄때나 state 업데이트를 할 때나 많은 사용을 한다

그 전에 할당 연산자를 알아보면

할당

우리가 변수를 선언한 후 값을 할당한다. 라는 얘기를 많이 들었을 것이다. 거기서 나온 것이 바로 할당이다

let x,y;
x = y = 9; // x = 9, y = 9
const z = (y++, x+y); z = 19

이와 같이 =를 활용 해서 할당하는 것이 바로 할당 연산자이다.
const z부분은 ( ) 안의 y++가 먼저 실행되어 y가 1이 증가해서 10+9가 돼서 19가 된다.

구조 분해 할당

뭔가 말이 이해된다면 이 연산을 쉽게 볼 수 있을 것이라 생각된다.
말 그대로 구조 자체를 분해해서 각각 할당하는 것인데

const u = {id: 1, name: 'lee', age: 29}
let {id, name, addr} = u
console.log(id,name,addr) // 1 , 'lee', 29

이처럼 객체나 배열등의 구조를 분해해서 각각 할당해주는 것인데 하나하나 파보면
선언된 u라는 오브젝트가 있고 아래에 let변수 선언문 뒤에 동일한 오브젝트 { }를 만들었다.
이후 안에 변수로 선언될 녀석들을 적는데 이 변수 이름들은 u라는 오브젝트의 키값과 동일하게 적는다.
이 변수 이름을 이용해 js가 u 오브젝트에서 해당 변수 이름을 키값으로 밸류 값을 찾아 해당 변수에 할당하게 된다.
이는 배열도 마찬가지인데

const arr = [1, 2, 3, 4, 5]
let [a,b,,,c] = arr
console.log(a,b,c) // 1,2,5

배열은 객체와 달리 키값이 눈에 보이지가 않는데 실제로는 배열도 객체로 각각 index: value라는 프로퍼티들로 이루어져 있다
Reflect.ownkeys를 사용해서 key값을 뽑아내면 실제로 0부터 시작한 index값들이 붙어있고 끝에 length라는 키값이 있는 걸 확인해 볼 수 있다.

그럼 구조분해 할당을 할 때 왜 [a,b,,,c]와 같은 불필요한 ,를 붙이는 것일까?
아까 오브젝트를 구조분해 할당 할 때 각 변수의 이름들을 이용해 키값으로 활용하여 밸류를 찾는다고 했는데 배열의 경우 배열에서 선언된 변수들의 위치 인덱스를 통해 할당할 arr의 배열 인덱스의 위치와 동일하게 변수를 할당하게 된다
따라서 만약 [a,b,c]로 선언하게 되면 1,2,3이라는 값이 되게된다
그러니 항상 주의해서 작성하자

왜 JS의 배열 끝에는 length라는 프로퍼티가 존재할까?
배열은 리스트와 같아서 트리 형태로 살펴본다면 각각의 인덱스 값에서 다음 값의 위치를 함께 가지고 있다.
0번째 인덱스를 찾으면 0번째 인덱스에서 1번째 인덱스로 내려오고 1번째 인덱스를 통해 2번째 인덱스로 내려오고 그런 방식이다 보니 중간에 하나가 빠지게 되거나 0번째 인덱스를 빼버리는 경우 배열 전체가 다시 설정되는 그런 불편함이 있다
그러다보니 보통 length라는 배열의 길이를 구하려면 0번째부터 마지막 인덱스까지 쭉 타고 들어가 총 배열 크기를 카운팅 해야 하는데 만약 우리가 자주 사용하는 for(let i=0; i<arr.length;i++)과 같이 반복문안에서 사용해 버리면 반복문이 실행할 때마다 그 긴 배열이 카운팅되어 제곱에 가까운 시간이 생기게된다.
그래서 실제로 arr.length를 다른 변수에 담아놓고 for문에서 사용하는 것이 카운팅 횟수를 줄일 수 있는 방법이다.
그렇기에 JS의 경우 배열이 만들어질 때 해당 배열의 크기를 length프로퍼티로 가지고 있게 하는 방법이 채택되었다고 볼수있다

복잡한 destructuring

좀 더 복잡한 분해 할당으로 들어가기 전 구조분해 할당 시 사용되는 간단한 연산자를 봐보자

const obj = {id:1,what:"lee"}
const {id,what:name,addr = 'seoul'} = obj
console.log(id,name,addr) // 1 , 'lee' , 'seoul'

이와 같이 :what을 통해 찾게된 value값을 내가 원하는 name라는 이름의 변수로 할당할 수 있게 변경해준다
뒤에 선언된 addr의 경우 obj에 해당 프로퍼티가 없는 것을 볼 수 있다. 이렇게 될 경우 addrundefined로 값이 할당되게 되는데 이는 매우 좋지 않으므로 =할당 연사자를 붙여 기본값을 설정해주는 것이다.
함수에서 파라미터 값을 =을 통해 기본값을 정해주는 것과 동일한 방법이다.

그럼 좀 더 복잡한 destructuring을 알아보자
아래 코드들은 연속적으로 작성된 하나의 프로세스 안에서 실행된 코드들이라고 생각하며 살펴보자

const user = {name:'Lee', age: 30}
const fn = ({age}) => age
console.log(fn(user)) // 30

한수안의 arguments로 해당 객체를 보내면서 도착했을 때 파라미터를 destructuring하여 age만 뽑아낸것이다.
이렇게 할 경우 파라미터를 객체로 받아 user.age와 같은 불필요한 코드를 짧게 줄일 수 있다.
이 방법은 React 프레임 워크를 사용할 때 많이 사용되니 꼭 알아두자

const {age2:age3 = fn(user)} = {age22: 20}; 
console.log(age3)//30
console.log(age2)// not defined

이는 구조분해 할당시 age2의 키값이 없으니 age2를 찾지 못했고 :를 통해 변수 이름은 age3로 변해서 age2not defiend라는 에러가 발생한 것
하지만 age3=연산자를 이용해 fn함수가 내뱉은 값이 할당되니 30이 출력되는 것이다
혹시 중첩되는 객체를 구조분해할당 하려고 하면

const u3 = {id:3, name: 'kim', addr: {id: 1, city: 'Seoul'}};
let {id:idd, addr:{id:aid}} = u3; // idd =3, aid = 1

똑같이 구조분해 할당을 하는데 addr을 통해 1번째 프로퍼티를 찾고 해당 프로퍼티 안을 {}구조분해 할당을 한번더 해서 그 안의 id를 찾게 되는 것이다.
여기서 사용되는 :는 이름 변경의 연산자가 아님을 꼭 기억하자

객체 / 배열 연산자

객체와 배열에서 사용할 수 있는 연산자들이 있다

  • 점(.)연산자 : u.name === u['name'] 키값을 검색해서 밸류값을 반환한다
  • 대괄호([])연산자 : 객체에서 []를 사용할 경우 키값을 적을 때 변수를 사용할 수 있다.[]를 사용할 때 대괄호 안에 키값을 string으로 주고 싶다면 꼭 ''을 사용하자
  • in 연산자 : 해당 프로퍼티가 있는지 확인해준다. u.hasOwnProperty('id') <==> Reflect.has(u,'id')
  • new 연산자 : const d = new Dog() class를 사용할 때 많이 봤을 것 같다
  • instanceof 연산자 : d instanceof Dog d라는 객체가 Dog라는 오브젝트의 프로토타입 체인에 걸리는지 확인한다
  • rest(...)연산자 : 아까 배열이나 객체 distructuring을 할 때 만약 나머지 값들을 넣고 싶다면 ...연산자를 통해 나머지 값들을 다 붙일 수 있다
  • delete 연산자 : 프로퍼티를 삭제해준다
  • arr?.length 연산자 : Optional-Chaining으로 에러를 내뱉지 않는다. 이전 연산자 1번부분에서 다룬 부분이니 확인해보자

rest(...)연산자는 spread와 동일하게 사요하지만 동작은 다르다.
spread를 더 쉽게 이해하고 싶다면 arr이란 [1,2,3,4,5]라는 배열이 존재할 경우 ...arr을 사용한다면 []이 벗겨진다고 생각하자.
만약 distructuring과 함께 사용한다면 rest로 동작하게 될텐데 const [a,b,...c] = arr을 사용한다면 ab는 각 인덱스 값들이 들어가지만 c는 나머지 [3,4,5]인 나머지 값들이 들어가게 된다.
객체에서 사용할 경우 미리 spread 연산자로 복사시킨후 해당 키값을 다른 값으로 다시 적으면 위에 덮어쓸 수 있게 된다

연습

user객체를 id와 name을 출력하는 3개의 함수로 만들어보자

const hong = {id:1, name:'hong'}

const f1 = (obj) => {
  console.log(`id:${obj.id},name:${obj.name}`)
}
const f2 = ({id,name}) => {
  console.log(`id:${id},name:${name}`)
}
const f3 = () => {
  console.log(`id:${this.id},name:${this.name}`)
}
const f4 = (id,name) => {
  console.log(`id:${id},name:${name}`)
}
const f5 = (...args) => {
  if(!args.length)return;
  const {id,name} = args;
  console.log(`id:${id},name:${name}`)
}

f1(hong)
f2(hong)
f3().bind(hong)()
f4(hong.id,hong.name)
f5(hong)
profile
https://mo-i-programmers.tistory.com/

0개의 댓글