MOA Webtoon Project: 웹툰모아 프로젝트

개발동기


웹툰 모아 (Webtoon MOA) 는 대표적인 웹툰 플랫폼 3사 네이버, 카카오페이지, 카카오웹툰(구 다음웹툰)의 웹툰들을 한 페이지에서 찾아 볼 수 있도록 만든 서비스이다.
웹툰 플랫폼의 다양화 및 컨텐츠 수가 증가함에 따라 사용자는 양질의 콘텐츠를 더 많이 이용할 수 있게 되었다. 하지만 원하는 웹툰을 보기 위해서는 여러 사이트를 방문해야 하는 불편함이 존재한다. 우리는 이러한 불편함을 해소하고자 대표적인 웹툰 플랫폼의 컨텐츠를 집약해 사용자의 편의성을 개선하는 서비스를 개발하였다.

역할

  • 백엔드
    기능 구현 및 크롤링 등

  • 프론트
    페이지 화면 제작 및 데이터 불러오기 등

  • 나의 역할
    헤더, 푸터, 검색창, 로그인, 회원가입, 아이디 찾기, 비밀번호 찾기 등의 페이지 제작 (퍼블리싱)

주요 기능


  • 다양한 방식으로 웹툰 리스트 보여주기
    여러가지 방식으로 웹툰 리스트를 보여주는 기능이다.
    해당 기능은 가장 메인이 되는 기능이다.
    처음 페이지에 접근하게 되면, 해당 요일과 전체 플랫폼을 기준으로 웹툰 리스트를 보여준다.
    요일과 플랫폼, 정렬 방식(인기순, 조회순)을 조건으로 설정할 수 있으며, 조건에 해당하는 웹툰들의 썸네일 이미지와 제목, 작가, 조회수 정보를 표기하여 보여준다.
    정렬 방식 중 인기순의 경우 각 유저들의 즐겨찾기 리스트에 가장 많이 추가가 된 순서로 정렬된다.
    조회순의 경우 해당 서비스에서 실제 웹툰 페이지로 이동할 때 발생하는 클릭 누적 횟수를 기준으로 정렬된다.

  • 웹툰 즐겨찾기
    각 웹툰 카드의 하트 토글을 누르면 동작한다.
    하트 토글을 누르게 되면 해당 유저의 즐겨찾기 리스트에 추가되며, 한 번 더 누르게 되면 즐겨찾기 리스트에서 삭제가 된다.
    이와같이 사용자가 즐겨찾기한 웹툰 리스트는 오른쪽 상단(인기순, 조회순 버튼 옆)의 하트 토글을 눌러 확인할 수 있다.

  • 검색
    페이지 상단의 검색창에 작가, 작품명을 키워드로 검색할 수 있다. 일치하는 결과가 있다면, 웹툰의 제목과 플랫폼, 작가 정보를 보여준다. 검색결과에서 제목을 클릭시 해당 웹툰으로 이동할 수 있다.

페이지 구성


  • 메인 화면 (웹툰 리스트)
  • 로그인
  • 회원가입
  • 아이디 찾기
  • 비밀번호 찾기
  • 검색

헤더

  • 웹툰 모아 첫번째 로고
    클릭시, 메인 페이지로 이동.

  • 검색란
    원하는 키워드 입력시 해당 키워드로 작가/작품명 검색.

  • 로그인 / 로그아웃
    로그인 하지 않았을 때는 로그인창으로 이동.
    로그인이 되어있는 상태일때는 로그아웃글자가 적혀있으며, 클릭시 로그아웃.

푸터

  • 웹툰 모아 두번째 로고
    로고 클릭시, 메인 페이지로 이동.

  • 정보
    웹툰 모아 개발자 및 개발 기간 등을 명시.

요일별 웹툰 정렬

요일별웹툰정리

  • 요일 정렬 Default
    기본적으로 해당 요일에 맞는 웹툰 리스트를 보여줌.
    오늘이 수요일이면, 수요일 웹툰 리스트를 보여줌.

  • 요일별 웹툰
    원하는 요일을 선택하여 해당 요일의 웹툰들을 볼 수 있음.

플랫폼별 웹툰 정렬

플랫폼별웹툰정리

  • 원하는 플랫폼만 보기
    네이버웹툰, 카카오페이지, 카카오웹툰 중 원하는 플랫폼 하나를 선택하여 해당 플랫폼의 웹툰만을 볼 수 있음.

  • 플랫폼 정렬 Default
    플랫폼 정렬의 기본은 3사 플랫폼 모두를 모아 보는 것임.

웹툰 즐겨찾기

즐겨찾기

  • 하트 토글
    하트 토글을 클릭하여 해당 웹툰을 즐겨찾기에 추가.
    빨간색이 된 하트를 한번 더 클릭시 즐겨찾기에서 삭제.
    우측 상단 하트 버튼을 클릭하여 사용자가 즐겨찾기한 웹툰만을 모아 볼 수 있음.

인기순 웹툰 정렬

인기순정렬(1)

  • 인기순
    해당 서비스를 이용하는 사용자들이 가장 많이 즐겨찾기 리스트에 추가한 순서.
  • 인기순 정렬로 인해 많은 사용자들에게 사랑받고 있는 웹툰들의 추세를 알 수 있음.

조회순 웹툰 정렬

조회순정렬(2)

  • 하트 옆 숫자
    해당 서비스에서 웹툰 카드를 클릭하여 해당 웹툰의 페이지로 이동한 횟수를 나타냄.

  • 조회순
    조회순 정렬 버튼을 클릭하면 조회가 가장 많은 웹툰부터 최상단에 정렬됨.

검색기능

검색기능

  • 검색란
    헤더 우측에 검색란에 원하는 키워드를 입력하여 검색.

  • 검색 결과
    해당 키워드가 들어가는 작품과 작가명들을 모두 보여줌.
    검색 결과로는 제목, 플랫폼, 작가명을 알려줌.
    해당 웹툰 클릭시 해당 웹툰 페이지로 이동.

  • 검색 결과 없음
    키워드에 해당하는 검색결과가 없을 경우, “검색 결과 없음”이라는 문구 등장.

로그인

로그인

  • 자사 로그인
    아이디와 비밀번호 입력후 파란색의 로그인 버튼 클릭시, 로그인 완료.

  • 소셜 로그인
    카카오와 네이버로 로그인 가능.
    카카오 및 네이버 로그인 버튼 클릭시, 회원가입이 되어있지 않을 경우 회원가입 페이지가 열림.
    회원가입이 완료되었다면 클릭시 팝업창이 뜬 후, 로그인이 완료됨.

회원가입

회원가입

  • 회원가입하기
    이름, 아이디, 비밀번호, 비밀번호 확인, 이메일을 모두 입력하면 가입완료.
    조건에 맞지 않거나 모두 입력하지 않았을 경우 팝업창으로 사용자에게 알려줌.

아이디 찾기

아이디찾기

  • 아이디 찾기
    이름과 이메일을 입력.
    회원가입한 이름과 이메일이 동일하지 않거나 존재하지 않는 계정이면 아이디 찾기가 불가하다는 경고문 알림.

  • 인증번호
    이름과 이메일이 일치하면 해당 이메일로 인증번호 전송.
    인증번호를 해당 페이지에 입력 후, 확인버튼을 클릭하면 해당 아이디를 알려줌.

비밀번호 찾기

비밀번호찾기

  • 비밀번호 찾기
    아이디와 이메일을 입력.
    일치하지 않을 시 경고문 알림.

  • 비밀번호 전송
    아이디와 이메일이 일치하면, 해당 이메일로 가입시 작성하였던 비밀번호를 전송.

프로젝트를 마치며


해당 프로젝트에서 내가 맡은 역할은 웹 퍼블리싱이었다.
아직 제대로된 프론트엔드공부를 하지 않아서 자바스크립트는 잘 다루지 못하는 것이 문제였다. 거의 모든 페이지를 제작하였는데, 저번 프로젝트에서 사용했던 것들을 다시 복습만을 하게 되었다. 새롭게 배운 것이 거의 없다보니 새로 배운 내용들을 작성할 것이 없다.
대신 이번 프로젝트를 하면서 깨달은 것은 난 정확히 무엇이 하고 싶은 것인가? 이다. 웹 퍼블리싱인지 프론트엔드인지.
내가 내린 결론은 프론트엔드이다. 웹 퍼블리싱, 충분히 좋지만 앞으로의 비전과 발전을 위해서는 프론트엔드가 맞는 것이고, 퍼블리싱의 길을 갈것이라면 디자인도 할 줄 알아야 한다고 생각이 들었다. 하지만 난 디자인은 완전 잼병..이다.
나는 프론트엔드 개발자의 길을 걸어가기로 완전히 정하게 되었다. 그렇기 위해서는 정말 자바스크립트 공부와 리액트 공부를 열심히 해야 한다는 것을 이번 프로젝트에서 절실히 느꼈다.
백엔드에서 크롤링한 데이터들을 제작한 페이지로 불러오는데 난 할줄 아는 것이 하나도 없었다. 심지어 프론트엔드 처음하시는 컴공분이 다 하셨다. 여기서 약간의 자존심 스크래치와 난 도대체 그동안 뭘 공부한거지?라는 약간의 회의감이 들었다. 그리고 아직 배울 것이 한참 남았다는 것을 깨닫게 된 프로젝트였다.
다음 프로젝트에서는 진짜 프론트엔드를 할 수 있도록 열심히 자바스크립트 기본 공부를 끝내고 리액트 공부를 해야 겠다.
이번 프로젝트는 새롭게 알게된 지식들은 별로 없었지만 해이해져있던 나에게 경각심과 뭘 공부해야 할지에 대한 방향을 얻을 수 있었던 프로젝트였다.

JavaScript: JS 데이터 Ep.4 (전개연산자, 불변성, 얕은복사, 깊은 복사)

전개 연산자 (Spread)


마침표 3개 (…)가 전개 연산자의 기호이다.

전개연산자는 하나의 배열데이터를 쉼표로 구분된 각각의 아이템으로 전개해서 출력한다.

const fruits = ['Apple', 'Banana', 'Cherry']
console.log(fruits)

// 전개 연산자
console.log(...fruits)
// 'Apple', 'Banana', 'Cherry'
// 아래의 방법으로 출력하는 것과 동일한 모양으로 나온다.
// console.log('Apple', 'Banana', 'Cherry')

function toObject(a, b, c) {
	return {
		a: a,
		b: b,
		c: c
	}
}
console.log(toObject(...fruits))
// {a: "Apple", b: "Banana", c: "Cherry"}
  • 전개연산자를 사용하지 않았을 때
    아래와 같이 작성해야 동일한 형태로 출력이 된다. 하나씩 수동으로 작성해야 하기 때문에 번거롭다. 또, 갯수가 많아질 수록 비효율적이게 된다.
    console.log(toObject(fruits[0], fruits[1], fruits[2]))
    // {a: "Apple", b: "Banana", c: "Cherry"}
    
  • 매개변수에 전개연산자 사용하기 → 나머지 매개변수(rest parameter)
    Apple은 a로, Banana는 b로 들어가게 된다. 나머지 Cherry와 Orange는 c로 들어간다. 매개변수의 전개연산자는 나머지의 모든 인수들을 다 받아내는 역할을 한다. 이것을 나머지 매개변수(rest parameter)이라고 부른다.

    const fruits = ['Apple', 'Banana', 'Cherry', 'Orange']
      
    function toObject(a, b, ...c) {
      return {
        a: a,
        b: b,
        c: c
      }
    }
    console.log(toObject(...fruits))
    // {a: "Apple", b: "Banana", c: Array(2) 
    // 0: "Cherry" 1: "Orange"}
    
  • 축약형으로 만들기
    • 속성의 이름 = 변수의 이름 (데이터의 이름)
      속성의 이름과 변수의 이름이 같을 때, 하나만 남겨두어도 된다.
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      function toObject(a, b, c) {
        return {
          a,
          b,
          c
        }
      }
      console.log(toObject(...fruits))
      // {a: "Apple", b: "Banana", c: "Cherry"}
      
    • 화살표 함수로 만들기
      객체데이터를 사용시에는 중괄호를 사용하는데, 중괄호는 화살표함수에서 함수의 범위를 나타내는 블럭의 의미로 해석이 된다. 중괄호가 블럭이 아닌 객체 데이터의 의미로 사용하기 위해서는 소괄호를 밖에 사용해주고 중괄호(객체 데이터 의미)를 사용해 주면 된다.
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const toObject = (a, b, c) => ({a, b, c})
          
      console.log(toObject(...fruits))
      // {a: "Apple", b: "Banana", c: "Cherry"}
      

불변성 (Immutability)


원시데이터

JS에서 사용할 수 있는 기본 데이터들을 의미한다.
String, Number, Boolean, undefined, null

참조형 데이터

Object, Array, Function

원시 데이터불변성

  • 생김새가 달라서 false가 아닌, 메모리의 주소가 다르기 때문에 일치하지 않는다.
    // main.js
        
    let a = 1
    let b = 4
    console.log(a, b, a === b) // 1, 4, false
        
    // ----------------------------------------
    // |1: 1    |2:  4   |3:    |4:   
    // ----------------------------------------
    
  • b 는 기존에 2번 메모리의 주소를 가지고 있었다. 그러나 b = a로 인해 b는 더이상 2번 메모리 주소가 아닌 1번 메모리 주소를 가지고 있게 되었다. 그렇기에 b와 a가 가지고 있는 주소가 동일해 지면서 true가 나온다.
    2번 메모리의 값은 4로 변하지 않는다. 변하는 것은 오직 b가 가지고 있는 메모리 주소값!
    // main.js
        
    let a = 1
    let b = 4
        
    b = a
    console.log(a, b, a === b) // 1, 1, true
        
    // ----------------------------------------
    // |1: 1    |2:  4   |3:    |4:   
    // ----------------------------------------
    
  • a = 7이라는 새로운 데이터가 생성된다. 숫자 데이터 7은 메모리 3번에 들어가면서 a는 1번 메모리 주소가 아닌 3번 메모리 주소를 갖게 된다.
    // main.js
        
    let a = 1
    let b = 4
        
    a = 7
    console.log(a, b, a === b) // 7, 1, false
        
    // ----------------------------------------
    // |1: 1    |2:  4   |3: 7   |4:   
    // ----------------------------------------
    
  • 변수 c에는 숫자 데이터 1을 할당했다. c에 할당된 숫자 데이터 1이 4번째 메모리에 들어가는 것이 아니라, c는 기존 숫자 데이터 1이 있는 1번 메모리 주소를 갖게 된다.
    // main.js
        
    let a = 1
    let b = 4
        
    let c = 1
    console.log(b, c, b === c) // 1, 1, true
        
    // ----------------------------------------
    // |1: 1    |2:  4   |3: 7   |4:   
    // ----------------------------------------
    
  • 생긴것이 다르면 다른 데이터!
    새로운 원시데이터를 사용했을 때, 원시 데이터가 기존에 메모리 주소에 들어있다면 해당 원시데이터를 새로운 메모리에 새롭게 만드는 것이 아니다. 기존에 존재하는 메모리 주소만 바라보도록 만들어 준다.
    즉, 원시데이터들은 새롭게 만들어 지는 것이 아닌 한번 만들어 지면 항상 불변한다는 개념이다. (기존에 데이터들은 변하지 않는다!)
    간단히 말하면, 원시형 데이터들은 생긴것이 다르면 다른 데이터라고 이해하면 된다. 생긴 것이 같으면 같은 데이터!

참조형 데이터가변성

참조형 데이터는 원시형 데이터와 다르게 새로운 값을 만들때 마다 새로운 메모리 주소에 할당되는 구조를 가지고 있다.
따라서, 참조형 데이터는 불변성이 없다. 즉, 가변성이다!

  • a와 b는 생김새는 같아도 서로 다른 메모리 주소를 바라보고 있기 때문에 false가 출력된다. a는 1번 메모리, b는 2번 메모리 주소를 갖고 있다.
    let a = { k: 1 }
    let b = { k: 1 }
    console.log(a, b, a === b)
    // { k: 1 } { k: 1 } false
        
    // ---------------------------------------------------
    // |1:  { k: 1 } |2: { k: 1 } |3: {     } |4: {     }
    // ---------------------------------------------------
        
    
  • a가 가지고 있는 메모리 주소의 데이터 k의 값을 7로 변경했다.
    b는 2번 메모리 주소를 가지고 있었지만, b = a로 인해 b는 a가 가지고 있는 메모리 주소 (1번 메모리)를 갖게 된다.
    동일한 메모리 주소를 가지고 있기 때문에 true값이 출력된다.
    let a = { k: 1 }
    let b = { k: 1 }
        
    a.k = 7
    b = a
    console.log(a, b, a === b)
    // { k: 7 } { k: 7 } true
        
    // ---------------------------------------------------
    // |1:  { k: 7 } |2: { k: 1 } |3: {     } |4: {     }
    // ---------------------------------------------------
        
    
  • a.k = 2를 통해 a의 메모리 주소의 값 (1번 메모리)이 2로 변경된다.
    b는 a와 동일한 주소를 가지고 있기 때문에 b도 1번 메모리 주소를 가지고 있다.
    a와 b의 값을 출력하면 k:2가 출력되면서 동일한 메모리 주소를 갖기에 true가 반환된다.
    let a = { k: 1 }
    let b = { k: 1 }
        
    a.k = 2
    console.log(a, b, a === b)
    // { k: 2 } { k: 2 } true
        
    // ---------------------------------------------------
    // |1:  { k: 2 } |2: { k: 1 } |3: {     } |4: {     }
    // ---------------------------------------------------
      ****
    

    주의할 점은, a의 k 속성값만을 바꾼 것인데 의도하지 않게 b의 k값도 변경이 된다. b가 a와 동일한 주소를 가지고 있기 때문이다.

  • a는 1번 메모리 주소, b도 1번 메모리 주소를 가지고 있다.
    c도 b를 할당 했기 때문에 c도 1번 메모리 주소를 갖게 된다.
    a, b, c 모두 동일한 메모리 주소를 갖게 되었다.
    let a = { k: 1 }
    let b = { k: 1 }
        
    let c = b
    console.log(a, b, c, a === c)
    // { k: 2 } { k: 2 } { k: 2 } true
        
    // ---------------------------------------------------
    // |1:  { k: 2 } |2: { k: 1 } |3: {     } |4: {     }
    // ---------------------------------------------------
      ****
    
  • 수정한 것은 a변수의 k속성이지만 a, b, c 모두가 동일한 1번 메모리 주소 (a의 메모리 주소)를 가지고 있기 때문에 동일한 값이 출력된다.
    let a = { k: 1 }
    let b = { k: 1 }
        
    a.k = 9
    console.log(a, b, c, a === c)
    // { k: 9 } { k: 9 } { k: 9 } true
        
    // ---------------------------------------------------
    // |1:  { k: 9 } |2: { k: 1 } |3: {     } |4: {     }
    // ---------------------------------------------------
    
  • a = b (참조형 데이터에서 할당연산자 사용)
    a가 가지고 있는 데이터가 복사되는 개념이 아닌 메모리의 주소만 같은 곳을 바라보도록 만들어 주는 것이기 때문에 a를 수정하면 b도 수정되고, b를 수정하면 a도 수정되는 현상이 발생한다.
    위의 방식은 의도한 것이 아니라면 일반적인 경우에는 복사를 사용하여 a 와 b를 메모리 상에서 분리해줘야 생각한 방식으로 사용이 가능하다.

얕은 복사(Shallow copy)와 깊은 복사(Deep copy)


복사를 사용하기 전

  • copyUser과 user는 같은 메모리 주소를 가지고 있다.
    const user = {
      name: 'Soha',
      age: 45,
      email: ['soha@gmail.com']
    }
    const copyUser = user
    console.log(copyUser === user) // true
        
    // |1:     |2:     |3:      |4:     
    
  • user.age의 값은 22로 변경했는데 copyUser의 age값도 22로 변경됐다. 둘이 동일한 메모리 주소를 바라보고 있기 때문이다.
    const user = {
      name: 'Soha',
      age: 45,
      email: ['soha@gmail.com']
    }
    const copyUser = user
        
    user.age = 22
    console.log('user', user) 
    // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
    console.log('copyUser', copyUser)
    // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
    

복사 사용하기

  • 메소드 assign을 사용한 복사 → 얕은 복사
    Object에 assign 메소드를 실행해서 첫번째 인수로 빈 객체 데이터, 두번째 인수로 복사한 user변수를 넣는다.
    첫번째 인수는 대상객체, 두번째 인수부터는 출처객체. 출처객체를 대상객체에 담아 반환한다.
    const user = {
      name: 'Soha',
      age: 45,
      email: ['soha@gmail.com']
    }
    const copyUser = Object.assign({}, user)
    console.log(copyUser === user) // false
        
    user.age = 22
    console.log('user', user)
    // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
    console.log('copyUser', copyUser)
    // { name: 'Soha', age: 45, email: ['soha@gmail.com']}
        
    console.log('------')
    console.log('------')
    

    중괄호를 사용한 객체 리터럴도 데이터이기 때문에 새로운 메모리 주소에 할당된다. 2번 메모리에 user가 가지고 있는 속성과 값이 들어가게 된다.

    따라서 user은 1번 메모리 주소, copyUser은 2번 메모리 주소를 갖고 있다. user.age를 22로 변경했을 때, copyUser의 age는 변경되지 않는 것을 볼 수 있다.

  • 전개 연사자를 사용한 복사 → 얕은 복사
    const copyUser = {...user}를 통해 copyUser가 새로운 메모리 주소에 할당되었다.
    하나의 빈 객체 데이터 내부에 user라는 객체 데이터가 가지고 있는 속성과 값들을 전개해서 집어넣게 된다.
    const user = {
      name: 'Soha',
      age: 45,
      email: ['soha@gmail.com']
    }
    const copyUser = {...user}
    console.log(copyUser === user) // false
        
    user.age = 22
    console.log('user', user)
    // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
    console.log('copyUser', copyUser)
    // { name: 'Soha', age: 45, email: ['soha@gmail.com']}
        
    console.log('------')
    console.log('------')
    
  • 얕은 복사(Shallow copy)

    user 부분에 emails라는 배열 데이터 부분에 push 메소드를 사용했다. push 메소드는 배열 데이터의 가장 뒷부분에 새로운 아이템으로 push 부분의 인수를 밀어 넣는다.

    const user = {
      name: 'Soha',
      age: 45,
      email: ['soha@gmail.com']
    }
    const copyUser = {...user}
    console.log(copyUser === user) // false
      
    user.age = 22
    console.log('user', user)
    // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
    console.log('copyUser', copyUser)
    // { name: 'Soha', age: 45, email: ['soha@gmail.com']}
      
    user.emails.push('noha@naver.com')
    
    • user.emails 와 copyUser.emails가 동일하다는 값이 나온다.
      복사를 통해 user와 copyUser는 달라졌는데 왜 일까?
      console.log(user.emails === copyUser.emails) // true
      // console.log('user', user)
      // console.log('copyUser', copyUser)
      

      user.emails는 배열데이터로 배열 데이터는 참조형 데이터이다.
      우리는 user.emails 배열데이터를 따로 복사처리를 한 적이 없다. 우리는 user라는 객체 데이터 하나를 복사처리 한 것이지 user. emails 배열 데이터를 복사한 것은 아니라는 것! 그래서 true값이 나오는 것이다.

      console.log(user.emails === copyUser.emails) // true
          
      console.log('user', user)
      // { name: 'Soha', age: 22, email: 
      // Array(2) 0: 'soha@gmail.com' 1: 'noha@naver.com'}
          
      console.log('copyUser', copyUser)
      // { name: 'Soha', age: 45, email: 
      // Array(2) 0: 'soha@gmail.com' 1: 'noha@naver.com'}
      

      copyUser의 emails에 추가한 적은 없지만, 따로 복사를 하지 않았기 때문에 같은 메모리 주소를 바라보게 되면서 copyUser의 emails와 user의 emails가 동일하게 변경되었다.

    • 얕은 복사
      겉 표면만 복사가 되었고 내부의 깊은 부분(email 배열 데이터)은 복사가 되지 않았다. 이것을 얕은 복사라고 한다.
  • 깊은 복사(Deep copy)

    JS만으로는 깊은 복사를 구현하기 어렵기 때문에 Lodash를 사용하여 깊은 복사를 할 것이다.

    • _ (언더바) 기호가 하나의 객체 데이터이다. (lodash 패키지 기능)
    • lodash 기능 뒤에 lodash 메소드인 cloneDeep() 메소드를 작성한다. 인수로 user라는 객체를 넣어주면서 깊은 복사가 된다.
      import _ from 'lodash'
          
      const user = {
      	name: 'Soha',
      	age: 45,
      	email: ['soha@gmail.com']
      }
      const copyUser = _.cloneDeep(user)
      console.log(copyUser === user) // false
          
      user.age = 22
      console.log('user', user)
      // { name: 'Soha', age: 22, email: ['soha@gmail.com']}
      console.log('copyUser', copyUser)
      // { name: 'Soha', age: 45, email: ['soha@gmail.com']}
      
    • user부분의 emails만 새로운 값이 push된다.
        user.emails.push('noha@naver.com')
        console.log(user.emails === copyUser.emails) // false
            
        console.log('user', user)
        // { name: 'Soha', age: 22, email: 
        // Array(2) 0: 'soha@gmail.com' 1: 'noha@naver.com'}
            
        console.log('copyUser', copyUser)
        // { name: 'Soha', age: 45, email: 
        // Array(1) 0: 'soha@gmail.com'}
      
  • 총 정리

    • 얕은 복사
      대표적인 참조형 데이터 (객체, 배열)를 복사할때는 내부에 또 다른 참조형 데이터가 없다는 전재하에 사용하기.
    • 깊은 복사
      내부에 또 다른 참조형 데이터가 있을 때 사용하기.

JavaScript: JS 데이터 Ep.3 (객체, 구조 분해 할당)

객체


메소드

Object ⇒ 전역 객체

  • Object.assign(대상객체, 출처객체)

    출처객체의 데이터가 대상객체에 추가가 되면서 대상객체의 데이터 내용은 기존 대상객체가 가진 데이터 + 출처객체 데이터로 새롭게 변경된다.

    assign 메소드는 만들어둔 userAge, userEmail에 직접 사용할 수 없다.
    assign은 Object라는 자바스크립트 전역객체의 프로토타입으로 만들어진 메소드가 아니기 때문에 일반적인 실제 객체 데이터(userAge 등)에 직접 사용을 할 수가 없다.
    그래서 Object라는 전역객체의 직접적으로 사용하는 메소드이며 이것을 정적 메소드(static) 라고 부른다.

    const userAge = {
      // key: value
      name: 'Soha',
      age: 45
    }
    const userEmail = {
      name: 'Soha',
      email: 'soha@gmail.com'
    }
      
    const target = Object.assign(userAge, userEmail)
    console.log(target)
    // name: 'Soha',
    // age: 45,
    // email: 'soha@gmail.com'
    console.log(userAge)
    // 위 코드 결과와 동일
    console.log(target === userAge)
    // true
    

    생긴것이 똑같아서 일치연산자를 사용했을 때 같다고 나오는 것이 아니다.

    • 동일한 생김새지만 일치연산자는 false
      const a = { k: 123 }
      const b = { k: 123 }
      console.log(a === b)
      // false
      

      해당 코드는 생김새가 완전히 동일하지만 일치연산자의 결과는 false이다. 이것은 생김새만 같을 뿐 동일한 것이 아니기 때문이다.
      즉, 나와 내 친구가 같은 기종의 핸드폰을 들고 있지만 소유주가 다르기에 다른 곳에 위치해 있는 것 이랄까?
      a의 데이터는 메모리 1번에, b의 데이터는 메모리2번에 저장되어 있다. 동일하게 생겼지만 다른 주소를 가지고 있기에 일치연산자를 했을 시 false라는 결과가 나온다.

      위의 target과 userAge는 메모리 주소가 동일하기 때문에 일치연산자 결과가 true로 나온다.
      userAge의 데이터가 1번 메모리에 저장되어 있다고 했을 때, userAge는 메모리 1번의 주소를 가지고 있다. target은 userAge의 값을 받기 때문에 target 또한 메모리 1번의 주소를 가지고 있게 된다.
      따라서, 일치연산자를 했을때 target과 userAge는 모두 메모리 1번의 주소를 가리키고 있기에 true값이 나오게 된다.

      • 참조형 데이터
        메모리에 있는 특정 주소를 참조만 해서 사용하는 것.
        객체, 배열, 함수가 이에 해당.
    • 새로운 객체를 만드는 방법

      const target = Object.assign({}, userAge, userEmail)
      console.log(target === userAge)
      // false
      

      대상객체는 객체 리터럴( {} )이 되면서 userAge와 userEmail은 출처객체가 된다. 빈 객체 데이터가 userAge와 userEmail의 데이터를 가지게 되면서 빈 객체 데이터가 target이라는 변수에 반환이 된다.
      이렇게 하면 userAge의 데이터는 변하지 않게 되면서 userAge와 userEmail을 합칠 수 있게 된다.

      const target = Object.assign({}, userAge)
      console.log(target)
      // name: 'Soha',
      // age: 45
      console.log(userAge)
      // name: 'Soha',
      // age: 45
      console.log(target === userAge)
      // false
      

      같은 데이터 값을 가지고 있지만 가지고 있는 주소가 다르기 때문에 false라고 나온다! 즉, 서로 다른 객체 라는 것

  • Object.keys

    객체 데이터의 속성 이름들을 배열로 만들어서 반환한다.

    const user = {
      name: 'Soha',
      age: 45,
      email: 'soha@gmail.com'
    }
      
    const keys = Object.keys(user)
    console.log(keys)
    // ['name', 'age', 'email']
    
    • 객체 데이터에서 대괄호를 사용하는 인덱싱 방법
      원하는 속성의 값을 가져오기
      console.log(user['email'])
      // soha@gmail.com
      
    • 객체 데이터의 값들만 추출하기
      인덱싱 방법을 통해 동적으로 객체 데이터 내에 있는 내용들을 가져와서 활용하는 방법

      const values = keys.map(key => user[key])
      console.log(values)
      // ["Soha", 45, "soha@gmail.com"]
      

      keys는 user라는 객체의 속성들을 배열 형태로 가지고 있는 배열데이터이다.

      배열데이터에는 직접적으로 map이라는 메소드를 사용할 수 있다.
      배열데이터가 가지고 있는 아이템의 갯수만큼 콜백함수를 반복적으로 실행한다. 콜백함수가 실행될때마다 아이템(name)이 key라는 매개변수에 들어가고 user[name]이 되면서 해당 속성의 값(’Soha’)이 반환된다.

      • 화살표 함수에서 중괄호가 작성되어져 있지 않으면 하나의 실행문이 콜백함수에서 밖으로 반환될 수 있는 구조이다.

      map이라는 메소드는 콜백에서 반환된 특정한 데이터를 배열로 만들어서 반환해주는 기능을 가지고 있다. 따라서 user라는 객체에서 key부분의 값들을 추출해서 배열로 만들어 반환하고 있다.values라는 변수에 반환 내용을 담고 있다.

구조 분해 할당


구조 분해 할당 (Destructuring assignment) = 비구조화 할당
user라는 객체 데이터에서 구조, 분해해서 원하는 속성들만 꺼내서 사용할 수 있게 하는 것이다.

const user = {
	name: 'Soha',
	age: 45,
	email: 'soha@gmail.com'
}
const { name, age, email, address } = user
// E.g, user.address

console.log(`사용자 이름은 ${name}입니다.`)
// 사용자 이름은 Soha입니다.
console.log(`${name}의 나이는 ${age}세입니다.`)
// Soha의 나이는 45세입니다.
console.log(`${name}의 이메일 주소는 ${email}입니다.`}
// Soha의 이메일 주소는 soha@gmail.com입니다.
  • 필요한 것만 꺼내오기

    email과 address가 필요 없다면 굳이 작성하지 않아도 된다.
    꺼내오고 싶은 것만 작성하면 된다.

    const { name, age } = user
    
  • 존재하지 않는 값은 undefined

    값이 존재하지 않기에 다음과 같은 결과가 출력

    console.log(address) 
    // undefined
    
  • 기본 값 지정해주기

    데이터를 가지고 있다면 가지고 있는 데이터를 그대로 사용하고 데이터가 없다면 (undefined) 기본값을 출력하도록 한다.

    const user = {
      name: 'Soha',
      age: 45,
      email: 'soha@gmail.com'
    }
    const { name, age, address = 'Korea' } = user
      
    console.log(address)
    // Korea
    
    const user = {
      name: 'Soha',
      age: 45,
      email: 'soha@gmail.com',
      address: 'USA'
    }
    const { name, age, email, address = 'Korea' } = user
      
    console.log(address)
    // USA
    
  • 다른 변수명으로 사용하기

    꺼내오는 것은 해당 속성의 이름으로 꺼내오지만 밑에서 변수로 활용할 때는 내가 지정한 이름(soha)으로 활용할 수 있게 된다.

    const user = {
      name: 'Soha',
      age: 45,
      email: 'soha@gmail.com'
    }
    const { name: soha, age, email, address } = user
      
    const soha = name
      
    console.log(`사용자 이름은 ${soha}입니다.`)
    // 사용자 이름은 Soha입니다.
    console.log(`사용자 이름은 ${name}입니다.`)
    // ReferenceError: name is not defined
    
    • name은 꺼내오는 용도로만 사용이 가능하기에 name으로 활용을 할 수 없게 된다.


  • 배열데이터 구조 분해 할당

    배열데이터의 구조 분해 할당은 이름으로 추출하는 것이 아닌 순서대로 추출을 해야 한다.

    const fruits = ['Apple', 'Banana', 'Cherry']
    const [a, b, c, d] = fruits
    console.log(a, b, c, d)
    // Apple Banana Cherry undefined
    
    • 꺼내올때는 사용하는 데이터 타입에 맞게 리터럴을 사용해야 한다. 객체는 {}, 배열은 []

    • 각각의 아이템들(‘Apple’, ‘Banana’, ‘Cherry’)을 순서대로 a, b, c, d에 넣는다.

    • d의 값은 존재하지 않기 때문에 undefined가 출력된다.

    • 원하는 값만 추출하기

      추출하기 원하지 않는 값은 변수명을 넣지 않고 쉼표로 칸만 만들어 준다.
      추출하려는 값에만 변수명을 넣어주고 해당 변수명을 출력하면 원하는 값만 추출할 수 있다.

      const [, b] = fruits
      console.log(b)
      // Banana
          
      const [, , b] = fruits
      console.log(b)
      // Cherry
      

InQ Project Manager: 프로젝트 페이지, 메인·등록·상세페이지 제작

프로젝트 페이지 구성


이번 프로젝트의 가장 핵심 기능 페이지인 프로젝트 페이지이다.
프로젝트 페이지는 메인 프로젝트 페이지, 프로젝트 등록 페이지, 프로젝트 상세 페이지로 총 3가지의 페이지로 구성되어 있다.

메인 프로젝트 페이지


프로젝트

등록된 프로젝트들을 모아둔 페이지이다.
상단의 모집중, 진행중, 완료 중에 하나를 선택하여 원하는 상태의 프로젝트 만을 모아서 볼 수 있다. 또, 상태 선택후 원하는 키워드의 프로젝트를 검색할 수 있다.

HTML

<div class="search-radio">
  <div class="inner">

    <div class="radio-box">
      <input type="radio" id="recruitment" name="state" value="recruitment">
      <label for="recruitment" class="bton underline">모집중</label>
    </div>

    <div class="radio-box">
      <input type="radio" id="progress" name="state" value="progress">
      <label for="progress" class="bton underline">진행중</label>
    </div>

    <div class="radio-box">
      <input type="radio" id="complete" name="state" value="complete">
      <label for="complete" class="bton underline">완료</label>
    </div>

    <div class="search">
      <span class="icon material-symbols-outlined">search</span>
      <input placeholder="프로젝트명으로 검색하기" type="text" name="search-text" id="search-text">
      <input type="submit" class="bton btn--reverse" value="검색">
    </div>
    
  </div>
</div>
  • 프로젝트의 상태를 선택하는 것은 input radio를 통해 하나의 상태만을 선택할 수 있도록 했다.
  • 상태 선택의 우측에는 키워드를 작성할 수 있는 검색란을 만들어 주었다.

CSS

/* Search - Radio */
.search-radio {
  margin-top: 200px;

}
.search-radio .inner {
  width: 2500px;
  display: flex;
}
.search-radio .radio-box input[type=radio] {
  width: 0;
  height: 0;
  left: -9999px;
}
.search-radio .radio-box input[type=radio] + label {
  font-size: var(--sub-title-font);
  margin-right: 20px;
  font-weight: 800;
  color: var(--inq-blue);
}
.search-radio .radio-box input[type=radio] + label:hover{
  background-color: transparent;
} 
/* Search - text*/
.search-radio .search {
  position: absolute;
  top: 55px;
  left: 800px;
  margin: auto;
}
.search-radio .search .icon {
  position: absolute;
  font-size: 70px;
  color: var(--inq-yellow);
}
.search-radio .search #search-text {
  width: 600px;
  margin-left: 80px ;
  margin-top: 10px;
  background-color: transparent;
  border: none;
}
.search-radio .search #search-text:focus {
  /* input 클릭시, 테두리 없애기 */
  outline: none;
  color: var(--inq-blue)
}
.search-radio .search #search-text::placeholder {
  color: var(--inq-blue);
  opacity: .6;
}
.search-radio .search input.bton {
  position: absolute;
  width: 100px;
  margin-left: 430px;
  top: 0;
}

radio 기본 스타일 없애기

  • input의 radio에는 기본적으로 적용되는 스타일이 있다. 글자 옆에 동그라미 선택 표시가 뜨는데, 해당 스타일을 원하지 않아 없앴다.
  • [type=radio]에 높이와 너비를 0으로 하고 화면에 보이지 않도록 left 속성을 -9999px로 함으로 위치를 않보이는 곳으로 옮겼다.

input 테두리 없애기

  • input 입력칸을 선택하면 파란색 테두리가 생기는데 해당 테두리를 없애기 위해 outline:none;을 사용하여 없앴다.

placeholder 스타일

  • placeholder의 색상은 기본적으로 검정색 글씨로 나온다. 해당 글씨색을 통일 성을 위해 변경해주고 싶었다.
  • ::placehlder을 통해 placeholder의 스타일을 변경할 수 있도록 하였다. color를 통해 색을 변경했고, 글자의 투명도를 주기 위해 opacity를 사용하였다.

프로젝트 등록 페이지


프로젝트 등록

프로젝트 등록 페이지는 본인이 프로젝트의 인원을 모집하고 싶을 때, 프로젝트에 대한 사항들을 작성 후 올릴 수 있도록 하는 페이지이다. 필수 입력을 한 후, 자유롭게 프로젝트 소개글을 작성하여 등록하면 프로젝트가 등록된다.
해당 페이지의 html과 css는 회원가입과 동일하여 따로 작성하지는 않겠다.

프로젝트 상세 페이지


프로젝트 상세

프로젝트 상세 페이지는 프로젝트 등록 페이지에서 등록한 포스팅이다.
프로젝트 생성자와 현재 프로젝트의 진행 상태, 모집기간과 프로젝트 기간 및 상세 설명 등을 볼 수 있다. 모집중인 역할이 무엇인지와 현재 참여한 멤버가 누구인지도 알 수 있다.
참여하기 버튼을 누르면 프로젝트에 참여가 된다.
또, 프로젝트를 등록한 회원에게만 프로젝트 상태변경 버튼이 보이게 되는데, 해당 버튼을 누르면 프로젝트 상태를 모집중, 진행중, 완료로 변경이 가능하다.
해당 페이지의 html과 css는 이전의 설명들과 크게 다를 것이 없어서 작성하지 않도록 하겠다.

프로젝트를 마무리 하며


해당 프로젝트는 나에게 새로운 지식과 경험을 할 수 있게 했던 프로젝트이다.
처음으로 다른 사람과 프로젝트를 진행해 보았고, 그로 인해 git을 굉장히 많이 활용할 수 있었다.
git에 관심이 많이 가서 오류 뜨는 것들도 엄청 찾아보면서 공부하고, 적용해보는 등.. 새로운 것을 배울 수 있었다.
그리고 프로젝트를 하면서 이전에는 해보지 않았던 모달창 만들기라던가 (비록 직접 만들기는 실패했지만..) 중복성을 줄이려고 jQuery를 이용한 header, footer 집어 넣기, 언더라인 이벤트 만들기, 물결 디자인 등…
주위에 도움을 요청할 곳 없이 오직 혼자서 구글에 서칭해가면서 프로젝트 하나를 다 완성하기는 처음인 프로젝트였다.
해당 프로젝트 덕분이 지금 마무리 되어가는 프로젝트를 만들면서도 정말 많이 도움이 되었다. 지금 하는 프로젝트는 이 프로젝트에서 배운것들을 다시 요긴하게 써먹으면서 하느라 크게 배운점이 없는 것 같아서 약간 아쉽긴 하지만…! 이건 해당 프로젝트 글에서 작성하도록 하고.!
최종적으로 해당 프로젝트를 하면서 팀플과 새로운 기능, git 사용법 등을 알 수 있게 되어서 좋았다. 정말 뜻 깊은 나의 첫 팀 프로젝트로 남을 것 같다.

+추가글
3월에 시작해서 5월에 끝난 프로젝트를 6월부터 작성해서 8월에 글을 다 썼다..ㅎ;; 이제 곧 끝나는 프로젝트는 빠르게 작성하도록 해볼 것이다…!

JavaScript: JS 데이터 Ep.2 (배열)

배열


인덱스, 인덱싱

const numbers = [1, 2, 3, 4]
const fruits = ['Apple', 'Banana', 'Cherry']

console.log(numbers) // (4) [1, 2, 3, 4]
console.log(fruits) // (3) ['Apple', 'Banana', 'Cherry']
console.log(numbers[1]) // 2
console.log(fruits[2]) // Cherry

메소드

  • length()
    배열의 길이를 알려준다. 즉, 배열안에 아이템이 몇개 들어있는 지를 알려준다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    console.log(numbers.length) // 4
    console.log(fruits.length) // 3
    console.log([1, 2].length) // 2
      
    console.log([].length) // 0
    
  • concat()
    두개의 배열 데이터를 병합하여 새로운 배열 데이터를 반환해주는 메소드이다. 원본의 데이터에는 손상이 없다!

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    console.log(numbers.concat(fruits)) // (7) [1, 2, 3, 4, 'Apple', 'Banana', 'Cherry']
    console.log(numbers) // (4) [1, 2, 3, 4]
    console.log(fruits) // (3) ['Apple', 'Banana', 'Cherry']
    
  • forEach()
    해당 메소드가 붙어있는 배열데이터의 아이템의 갯수만큼 특정한 콜백 함수를 반복적으로 실행하는 용도로 사용되는 메소드이다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    fruits.forEach(function (element, index, array) {
      console.log(element, index, array)
    }) 
        
    // Apple 0 
    // (3) ['Apple', 'Banana', 'Cherry']
    // Banana 1 
    // (3) ['Apple', 'Banana', 'Cherry']
    // Cherry 2 
    // (3) ['Apple', 'Banana', 'Cherry']
      
    fruits.forEach(function (fruit, i) {
      console.log(fruit, i)
    }) 
    
    • element
      배열데이터의 아이템을 하나씩 뽑아낸다.
    • index
      0부터 순차적으로 아이템의 갯수만큼 늘어난다.
    • array
      해당 배열데이터를 지칭한다.
    • 매개변수의 이름은 본인이 이해할 수 있는 것으로 변경해도 된다.
      단, 매개변수와 아래의 이름은 동일해야 실행이 된다.

      fruits.forEach(function (fruit, i) {
        console.log(fruit, i)
      }) // 실행됨
          
      fruits.forEach(function (item, i) {
        console.log(fruit, i)
      }) // 실행안됨
      
    • 마지막 array는 잘 사용하지 않는다.
    • 화살표 함수로 만들기
      자세한 코드 내용은 하단 map() 참고
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const a = fruits.forEach((fruit, index) => {
        console.log(`${fruit}-${index}`)
      })
      // Apple-0
      // Banana-1
      // Cherry-2
      
  • map()
    인수로 사용하는 콜백의 내부에서, 반환한 데이터를 가지고 그 데이터들을 모아놓은 새로운 배열을 만들어서 반환한다.
    원본 데이터에는 영향이 가지 않는다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    const a = fruits.forEach(function (fruit, index) {
      console.log(`${fruit}-${index}`)
    })
    // Apple-0
    // Banana-1
    // Cherry-2
    console.log(a) // undefined
      
    const b = fruits.map(function (fruit, index) {
      return.log(`${fruit}-${index}`)
    })
    console.log(b)
    // (3) ["Apple-0", "Banana-1", "Cherry-2"]
    
    • 반한된 것은 b라는 변수에 할당되었다.
    • 객체 리터럴 방식으로 작성하기
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const b = fruits.map(function (fruit, index) {
        return {
          id: index,
          name: fruit
        }
      })
      console.log(b)
      // 0: {id: 0, name: "Apple"}
      // 1 ...
      
    • 화살표 함수로 작성하기 + 단축시키기
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const b = fruits.map((fruit, index) => ({
        id: index,
        name: fruit
      }))
      console.log(b)
      // 0: {id: 0, name: "Apple"}
      // 1 ...
      
  • filter()
    특정한 배열데이터에서 필요해 의해서 원하는 조건으로 내용을 필터링 하여 해당 조건에 충족하는 데이터들로만 새로운 배열을 만든다.
    원본 데이터에는 영향이 가지 않는다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    const a = numbers.map(number => {
      return number < 3
    })
    console.log(a)
    // (4) [true, true, false, false]
      
    const b = numbers.filter(number => {
      return number < 3
    })
    console.log(b)
    // [1, 2]
    
    • 화살표 함수 단축시키기
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const b = numbers.filter(number => number < 3 )
      console.log(b)
      // [1, 2]
      
  • find()
    원하는 조건의 아이템을 찾게 되면 반복이 멈추면서 해당 값이 반환되고 끝이난다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    const a = fruits.find(fruit => {
      return /^B/.test(fruit)
    })
    console.log(a) //Banana
    
    • 정규표현식
      ^ ⇒ 시작한다.
      /^B/ ⇒ 대문자 B로 시작하는 문자데이터를 의미한다.

    • /^B/.test(fruit)
      대문자 B로 시작하면 true, 아니면 false

    • B로 시작하는 단어인 Banana가 fruit가 되면서 true가 반환이 되면, 아이템의 반복이 멈추게 된다. (즉, Cherry까지는 가지 않는다.)

    • 화살표함수의 간소화

      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const a = fruits.find(fruit => return /^B/.test(fruit))
      console.log(a) //Banana
      
  • findIndex()
    원하는 조건의 아이템을 찾게 되면 반복이 멈추면서 해당 아이템의 인덱스 번호를 반환한다.
    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    const a = fruits.findIndex(fruit => {
      return /^B/.test(fruit)
    })
    console.log(a) 
    // 1
    
    • 화살표 함수의 간소화
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      const a = fruits.findIndex(fruit => /^B/.test(fruit))
      console.log(a) 
      // 1
      
  • includes()
    인수로 사용되는 특정한 데이터(3, ‘SOHA’)가 해당하는 특정 배열(numbers, fruits)에 들어져 있는지 확인할 때 사용한다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    const a = numbers.includes(3)
    console.log(a) // true
      
    const b = fruits.includes('SOHA')
    console.log(b) // false
    
  • push()
    배열데이터의 원본이 수정된다!!
    해당 배열의 가장 뒤쪽에 특정한 인수의 내용을 밀어넣는(push, 추가하는) 것이다.
    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    numbers.push(5)
    console.log(numbers)
    // (5) [1, 2, 3, 4, 5]
    
  • unshift()
    배열데이터의 원본이 수정된다!!
    해당 배열의 가장 앞쪽에 특정한 인수의 내용을 밀어넣는(unshift, 추가하는) 것이다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    numbers.unshift(0)
    console.log(numbers)
    // (5) [0, 1, 2, 3, 4]
    
  • reverse()
    배열데이터의 원본이 수정된다!!
    해당 배열데이터의 순서를 거꾸로 뒤짚는다.

    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    numbers.reverse()
    fruits.reverse()
      
    console.log(numbers) // [4, 3, 2, 1]
    console.log(fruits) // ["Cherry", "Banana", "Apple"]
    
  • splice()
    배열데이터의 원본이 수정된다!!
    splice(인덱스 번호, 해당 인덱스 번호에서 아이템을 지우는 갯수)
    특정한 인덱스 번호의 숫자를 가져와서 원하는 갯수의 아이템을 해당 갯수만큼 지워준다.
    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    numbers.splice(2, 1)
      
    console.log(numbers) // (3) [1, 2, 4]
    -----------------------------
    
    const numbers = [1, 2, 3, 4]
    const fruits = ['Apple', 'Banana', 'Cherry']
      
    numbers.splice(2, 2)
    console.log(numbers) // (2) [1, 2]
    
    • 0개의 아이템 지우기 + 새로운 아이템 값 끼워넣기
      const numbers = [1, 2, 3, 4]
      const fruits = ['Apple', 'Banana', 'Cherry']
          
      // 0개의 아이템 지우기 - 그대로 출력
      numbers.splice(2, 0)
      console.log(numbers) // (4) [1, 2, 3, 4]
          
      // 새로운 아이템 끼워넣기
      numbers.splice(2, 0, 999)
      console.log(numbers) // (5) [1, 2, 999, 3, 4]
          
      fruits.splice(2, 0, 'Orange')
      console.log(fruits) // ['Apple', 'Banana', 'Orange', 'Cherry']
          
      // 원하는 아이템 지우고 그 자리에 새로운 값 넣기
      numbers.splice(3, 1, 99)
      console.log(numbers) // (5) [1, 2, 999, 99, 4]