ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [한입챌린지] 5일차 - JS 원시타입, 객체타입, 배열과 객체 반복문
    Front-end 개발 2024. 3. 31. 16:24

     

    자바스크립트의 두 가지 타입의 동작


    원시타입의 동작

    - 원시타입과 객체타입은 값이 저장되거나 복사되는 과정이 서로 다르기 때문이다.

    - 원시 타입의 경우에는 값 자체로써 변수에 저장되고 또 복사되는 반면에, 객체 타입의 경우에는 값 자체가 아닌, 값에 접근할 수 있는 주소 값이라 불리는 특별한 참조 값을 통해서 변수에 저장되고 또 복사된다.

    - 아주아주 중요한 포인트는 원시 타입의 경우 변수의 값을 1에서 2로 변경을 하게 되더라도 메모리 공간에

    저장되어 있었던 값은 실제로 수정되지 않는다. 대신에 변경해야 할 값을 새로운 메모리 공간에 추가적으로 저장하고 이어서 변수가 가리키던 주소 공간을 추가된 주소 공간을 가리키게 한다.

     

    - 원시 타입의 값들은 값이 저장되고 복사되고 변경되는 과정에서 변수의 값이 1에서 2로 수정되더라도 메모리에 한 번 저장해 두었던 원본 데이터가 수정되지 않기 때문에 원시 타입의 값들을 특별히 불변값이라고 부른다. 다시 정리하면 변수의 값을 바꿀 수 없는 상수여서 불변이라는 의미가 아니라, 메모리 공간에 저장된 원본 데이터의 값은 변경되지 않는다 라는 의미의 불변이라는 점을 꼭 기억하자. (원시 타입 = 불변값)

     

    객체타입의 동작

    - 객체 타입의 값들은 원시 타입의 값들과 다르게 배열이나 객체처럼 여러 개의 값을 저장함과 동시에 또 저장하는 값의 개수가 동적으로 유연하게 늘어났다 줄어들었다 하기 때문에 별도의 메모리 공간에 보관하는 것이라고 생각하면 된다.

    - 새로운 객체를 선언해서 이전 객체의 할당하면, 새로운 객체는 원시타입의 동작과 달리 메모리 상에서 이전 객체 변수가 가리키고 있던 참조 값을 새로운 객체도 똑같이 가리키게 된다. (원시타입처럼 메모리 공간에 데이터를 추가적으로 생성하지 않는다.) 이때 새로운 객체에 프로퍼티를 변경하는 코드를 작성하면 새로운 메모리 공간을 확보하는 것이 아닌 가리키고 있던 이전 객체의 원본 데이터를 변경한다. 즉, 새로운 객체만 변경되는 것이 아니라 이전 객체도 함께 바뀌어 버린다.

    - 원시타입과 객체타입의 차이는 메모리 상에서 값이 수정된다. (객체 타입 = 가변값)

     

    - 원시타입과 객체 타입의 차이는 원시타입은 값 자체로써 변수에 저장되고 복사 되기 때문에 변수의 값을 수정하더라도 저장된 원본 데이터는 수정되지 않기 때문에 불변값이라고 부르는 반면, 객체는 타입의 경우에는 값은 별도로 메모리 공간에 따라 보관해두고 참조값을 통해 변수에 저장되고 복사가 되기 때문에 특정 프로퍼티의 값을 수정하게 되면 메모리에 저장된 원본 데이터 자체가 수정되기 때문에 가변값이라고 부른다.

    타입 값의 형태 메모리 공간 수정시 변화
    원본 타입 불변값 원본 데이터 추가 메모리 공간 확보
    객체 타입 가변값 참조값과 원본 데이터 기존 메모리 공간 활용

     

    객체 타입 주의사항

    (1) 의도치 않게 값이 수정될 수 있다.

    - 다른 객체의 참조값을 가리키고 있는 객체를 수정했을 때, 해당 객체 및 다른 객체 모두 의도하지 않게 모두 수정하거나 아니면 수정되었다는 사실 자체를 모르고 있을 경우에는 꽤나 큰 오류가 발생할 수 있는 위험한 상황이 될 수 있다.

    - 의도하지 않았는데 하나의 변화가 또 다른 변수의 변화를 가져오는 현상을 Side Effect라고 부른다.

    - 객체의 값을 복사할 때는 그냥 대입 연산자로 변수의 참조 값 자체를 복사하도록 하는 게 아니라 대신에 새로운 객체 리터럴을 생성하고 그 내부에 스프레드 연산자 등을 이용해 내부 프로퍼티만 따로 복사해오는 방식으로 객체를 복사해야 한다. 그러면 아예 새로운 객체를 생성해서 초기화하는 것으로 평가되어 새로운 참조값에 새로운 객체 데이터가 따로 저장된다.

    - 내부 프로퍼티를 따로 복사해서 새로운 객체를 만들면, 새로운 객체의 프로퍼티를 변경한다고 하더라도 기존 객체와 새로운 객체는 서로 다른 참조값을 갖는 객체이기 때문에 다른 객체에 영향을 주지 않다. 그래서 조금 더 안전하게 객체의 값을 수정할 수 있게 된다.

    - 이때, 객체의 참조값을 그대로 대입 연산자를 통해서 복사하는 방식을 얕은 복사라고 부르며, 반대로 아래 그림의 오른쪽 방식처럼 새로운 객체를 생성하면서 내부 프로퍼티만 따로 복사해주는 방식을 깊은 복사라고 표현한다.

     

    - 정리하면, 얕은 복사는 원본 객체가 수정되는 상황이 발생할 수 있어서 신경 쓰지 않으면 위험할 수 있는 상황이 많이 발생할 수 있는 방식이지만, 깊은 복사는 원본 객체랑은 다른 새로운 객체를 만들어내는 방식으로 동작 하기 때문에 원본 객체 자제가 수정될 일이 없어서 비교적 훨씬 더 안전한 방식이다.

     

    (2) 객체간의 비교는 기본적으로 참조값을 기준으로 이루어진다.

    - 아래 이미지를 보면 o2는 얕은 복사로 o1과 같은 참조값을 갖으며 결국 같은 객체를 가리킨다.

    - 반면 o3는 깊은 복사로 객체를 생성하면서 spread 연산자를 이용해서 o1의 객체의 프로퍼티만 복사해줬다. 그 결과를 보면 메모리 상에서 새로운 객체가 하나 생성이 되고, 새로운 참조값으로 데이터를 가리키는 방식으로 메모리에 저장되었다.

    - 만약 o1과 o2를 일치 연산자(===)로 비교를 하면 결괏값으로 true가 나온다. 현재 같은 구조의 객체를 보관하고 있을 뿐만 아니라 참조값까지 동일하기 때문에 당연히 서로 같은 값으로 평가된다.

    - 그리고 o1과 o3를 일치 연산자로 비교하면 결과값은 false가 나온다. 일단 프로퍼티의 구조만 보면 name  프로퍼티를 가지는 o1과 o3는 같은 객체이다. 그러나 이 객체 간의 비교 연산은 기본적으로 참조값을 기준으로 이루어지기 때문에 참조값이 서로 다른 두 객체는 다르다는 평가 결괏값 false가 나오게 된다.

     

    - 참조값이 아닌 프로퍼티를 기준으로 두 객체를 비교하고 싶다면, JSON.stringify() 같은 객체를 문자열로 형변환하는 내장함수를 이용해서 참조값이 아닌 프로퍼티를 기준으로 비교하도록 설정해 주어야 한다.

    let o1 = {name: "홍길동"};
    let o2 = o1;
    let o3 = {...o1};
    
    console.log(o1 === o2); // true
    console.log(o1 === o3); // false
    
    console.log(
        JSON.stringify(o1) === JSON.stringify(o3)
    ); // true

     

    - 이때 참조값을 기준으로 비교하는 방식을 얕은 비교, JSON.stringify()와 같이 객체를 문자열로 변환하여 내부 프로퍼티를 기준으로 비교하는 방식을 깊은 비교라고 표현한다. 정리하면, 객체값을 서로 비교할 때 객체의 참조값이 같은지 비교하려면 얕은 비교를, 그게 아닌 객체의 프로퍼티의 값을 기준으로 비교하려면 깊은 비교를 수행한다.

     

    (3) 배열과 함수도 사실 객체이다.

    - JavaScript의 배열과 함수는 특수한 객체들이기 때문에 배열과 함수 또한 일반 객체에 존재하는 프로퍼티와 메서드를 다 가지고 있다. 배열과 함수는 일반 객체에 추가적인 프로퍼티나 메서드를 가진다고 이해할 수 있다.

    반복문으로 배열과 객체 순회하기


    순회(Iteration)라는 건 간단히 말해서 배열이나 객체에 저장된 여러 개의 값에 순서대로 하나씩 접근하는 것을 말한다. 배열을 순회한다면 인덱스를 기준으로 순서대로 값에 접근하는 것이고, 객체를 순회한다면 객체의 프로퍼티 key 또는 value를 기준으로 접근한다.

     

    배열의 순회

     

    (1) 배열 인덱스

    - 배열을 순회하는 첫 번째 방법은 배열의 인덱스를 통해 순회하는 것이다.

    - 배열이나 함수도 객체이기 때문에 프로퍼티 또는 메서드를 가질 수 있다. 메서드 .length는 배열의 프로퍼티로 모든 배열이 가지고 있는 기본적인 프로퍼티이면서 배열의 길이를 저장하고 있는 프로퍼티이다.

    - 아래 코드는 for 문과 배열의 인덱스를 이용하여 특정 배열을 순회하는 예시이다.

    // 1. 배열 순회
    let arr = [1,2,3];
    
    // 1.1 배열 인덱스
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
    
    let arr2 = [4, 5, 6, 7, 8];
    for (let i = 0; i < arr2.length; i++) {
        console.log(arr2[i]);
    }

     

    (2) for of 반복문

    - 배열을 순회하는 두 번째 방법은 for of 라는 특수한 반복문을 이용해서 순회를 진행한다.

    - for of 반복문은 of 뒤에 오는 배열의 값을 하나씩 순서대로 꺼내서 카운터 변수에 저장한다.

    - 첫 번째 방법과 두 번째 방법의 성능적 차이는 없다. 그러나 한 가지 차이점은 배열의 인덱스를 이용하는 방식은 카운터 변수에 배열의 인덱스가 저장되기 때문에 for 문 안에서 인덱스를 통한 작업을 할 수 있는 반면, for of 반복문은 인덱스를 저장하지 않고 그냥 뱅ㄹ에 있는 값들을 순서대로 순회만 해준다는 것이다.

    - 작업을 하는데 좀 더 편한 방식으로 둘 줄에 하나를 골라서 사용하면 된다.

    // 1.2 for of 반복문
    for (let item of arr) {
        console.log(item);
    }

     

    객체의 순회

     

    (1) Object.keys() 내장함수

    - Object.keys()라는 내장함수는 인수로 주어진 객체에서 key 값들만 뽑아서 새로운 배열로 반환한다.

    - Object.keys() 내장함수를 통해 반환된 새로운 배열을 통해 for 문는 또는 for of 반복문으로 순회할 수 있다.

    - 이때 추가로 key 값과 함께 객체의 value 값도 동시에 순회하고 싶다면 대괄호 표기법을 사용하면 된다.

    // 2.1 Object.keys 사용
    // -> 객체에서 key 값들만 뽑아서 새로운 배열로 반환
    let keys = Object.keys(person);
    console.log(keys);
    
    // for (let i=0; i < keys.length; i++) {
    //     console.log(keys[i]);
    // }
    
    for (let key of keys) {
        const value = person[key];
        console.log(key, value);
    }

     

     

    (2) Object.values() 내장함수

    - Object.values() 내장함수는 인수로 주어진 객체에서 value 값들만 뽑아서 새로운 배열로 반환한다.

    - 첫번째 방식과 동일하게 반환된 배열을 통해 for문 또는 for of 반복문으로 순회를 하면 된다.

    - 만약 객체를 순회할 때, value 값들만 순회하면 된다면 Object.values() 내장함수를 이용하는 방식을 사용할 수 있다.

    // 2.2 Object.values
    // -> 객체에서 value 값들만 뽑아서 새로운 배열로 반환
    let values = Object.values(person);
    console.log(values);
    
    for (let value of valeus) {
        console.log(value);
    }

     

    (3) for in 반복문

    - for in 반복문은 객체만을 위해 존재하는 특수한 반복문이다.

    - for in 반복문의 사용법은 for of 반복문의 사용법과 비슷하다. in 뒤에 있는 객체의 프로퍼티 key를 순서대로 카운터 변수에 할당한다.

    - for in 반복문을 사용하면 Object.keys() 또는 Object.values() 내장함수를 사용하지 않고도 객체를 순회할 수 있다.

    - 주의할 점은 for of 반복문과 for in 반복문을 헷갈리면 안된다. for of 반복문은 배열에만 쓸 수 있고, for in 반복문은 객체에만 쓸 수 있다. 객체 프로퍼티를 검사할 때 in 연산자를 사용했던 것을 떠올리면 된다.

    // 2.3 for in 반복문
    for (let key in person) {
        const value = person[key];
        console.log(key, value);
    }
    
    // chapter15 - 프로퍼티의 존재 유무를 확인하는 방법 (in 연산자)
    console.log('name' in person);
    console.log('hobby' in person);

     

     

     

    출처


    (강의) 한입 크기로 잘라 먹는 리액트

    (코드) GitHub - 강의 노트

     

Designed by Tistory.