객체와 배열 고급

나혜수·2023년 2월 6일
0

자바스크립트 

목록 보기
11/14

✅ 객체 내부 속성 존재 여부 확인

객체 내부에 어떤 속성이 있는지 확인해보는 코드는 굉장히 자주 사용되는 코드이다. 객체의 없는 속성에 접근하면 undefiend 자료형이 나온다. 따라서 조건문으로 undefined 인지 아닌지 확인하면 속성 존재 여부를 확인할 수 있다.

if (object.name !== undefined) {
  console.log('name 속성이 있습니다.')} 
else {
  console.log('name 속성이 없습니다.')
}   

위의 코드보다 더 간단히 검사하기 위한 코드는 아래와 같다. undefinednull은 논리 연산에서는 false로 처리된다. 객체의 특정 속성이 false로 변환될 수 있는 값(0, false, 빈 문자열 등)이 아닐 때 사용하는 코드이다.

if (object.name) {
  console.log('name 속성이 있습니다.')} 
else {
  console.log('name 속성이 없습니다.')
}   
object.name || console.log('name 속성이 있습니다.')}

// 객체의 기본 속성 지정 
object.name = object.name || '제목 미정'

❗단축 평가
1) object.name이 undefined라면 fasle로 평가되므로 undefined 반환
2) object.name 값이 존재하면 console.log 출력

단축 평가를 활용해 객체의 기본 속성을 지정하는 경우도 많다.


✅ 구조 분해 할당

구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.

객체와 배열은 자바스크립트에서 가장 많이 쓰이는 자료 구조이다. 개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기는데, 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 있다. 이럴때 구조 분해 할당을 이용할 수 있다. 이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해는 그 진가를 발휘한다.

할당 연산자 우측엔 모든 이터러블이 올 수 있다. 배열뿐만 아니라 모든 iterable (반복 가능한 객체)에 구조 분해 할당을 적용할 수 있다.
할당 연산자 좌측엔 ‘할당할 수 있는’ 것이라면 어떤 것이든 올 수 있다. 아래와 같이 객체 프로퍼티도 가능하다.

let user = {};
[user.name, user.surname] = "Bora Lee".split(' ');
alert(user.name); // Bora

배열 분해

배열이 어떻게 변수로 분해되는지 예제를 통해 살펴보자.

// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]

/* 구조 분해 할당을 이용해
firstName엔 arr[0]을 surname엔 arr[1]을 할당하였습니다. */
let [firstName, surname] = arr;

alert(firstName); // Bora
alert(surname);  // Lee

인덱스를 이용해 배열에 접근하지 않고도 변수로 배열의 요소를 사용할 수 있으며, 배열의 요소를 직접 변수에 할당하는 것보다 코드 양이 줄어든다.
아래 예시처럼 반환 값이 배열인 split 메서드를 함께 활용해도 좋다.

let [firstName, surname] = "Bora Lee".split(' ');

1. 기본값

할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 에러가 발생하지 않는다. 할당할 값이 없으면 undefined로 취급되기 때문이다.
'=' 을 이용하면 할당할 값이 없을 때의 기본값을 설정할 수 있다.

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined
// 기본값
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)

복잡한 표현식이나 함수 호출도 기본값이 될 수 있다. 기본값으로 두 개의 prompt 함수를 할당한 예시를 살자. 값이 제공되지 않았을 때만 함수가 호출되므로 prompt는 한 번만 호출된다.

let [surname = prompt('성을 입력하세요.'), name = prompt('이름을 입력하세요.')] = ["김"];

alert(surname); // 김 (배열에서 받아온 값)
alert(name);    // prompt에서 받아온 값

2. 쉼표를 사용하여 요소 무시하기

쉼표를 사용하면 필요하지 않은 배열 요소를 버릴 수 있다.

let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

3. 변수 교환 트릭

두 변수에 저장된 값을 교환할 때 구조 분해 할당을 사용할 수 있다.

let guest = "Jane";
let admin = "Pete";

[guest, admin] = [admin, guest];

console.log(guest, admin) // Pete Jane (값 교환이 성공적으로 이뤄졌습니다!)

객체 분해

객체 내부의 속성을 꺼내서 변수를 할당할 수 있다.

할당 연산자 우측엔 분해하고자 하는 객체를, 좌측엔 상응하는 객체 속성 이름을 넣는다. 객체 속성과 같은 이름을 가진 변수에 객체 속성 값이 할당된다.

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width} = options;

alert(title);  // Menu
alert(width);  // 100

분해하려는 객체의 속성과 다른 이름을 가진 변수에 속성 값을 저장할 땐 콜론을 이용한다. 속성 이름 : 변수

let {width: w, height: h, title} = options;

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

속성이 없는 경우를 대비하여 '=' 을 사용해 기본값을 설정하는 것도 가능하다. 변수 = 속성 이름
배열과 함수의 매개변수에서 했던 것처럼 객체에도 표현식이나 함수 호출을 기본값으로 할당할 수 있다. 물론 표현식이나 함수는 값이 제공되지 않았을 때만 실행된다.

let options = {
  title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

✅ 얕은 복사와 깊은 복사

자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터이다.
이는 Memory Heap & Call Stack 으로 구성되어 있다. 자바스크립트는 단일 스레드 (sigle thread) 프로그래밍 언어인데, 이 의미는 Call Stack이 하나라는 것이다. (하나의 스레드 당 하나의 스택 메모리를 사용)

위 코드가 Call Stack 영역과 Heap 영역에서 어떻게 동작하는지 알아보자.

const a = 'Hello World!';
const b = [1, 2, 3];
const c = { id:'yoy', password:1234 };
const d = function(){ console.log() };

우선 각 변수 a, b, c, d는 call stack의 실행컨텍스트 렉시컬환경이라는 곳에 { name : value } 형태로 저장된다. 비록 초기화 단계에서는 { 변수이름 : undefined } or { 함수이름 : function() object } 로만 저장되지만, 그래도 해당 변수의 이름을 식별할 수 있다. 이것을 식별자 해결 이라고 부른다.

자바스크립트에서 값은 원시값과 참조값으로 나뉜다.

  • 원시값 : Number, String, Boolean, Null, Undefined
  • 참조값 : Object, Array, Function, Symbol,
  1. 원시타입 ( a )
    콜스택 메모리에는 단순 변수와 같은 원시 타입 데이터가 저장된다. 변수 a는 콜스택 영역에 있는 a의 주소를 읽고, 그 콜스택 영역에 그대로 'Hello World'라는 값 자체가 저장되어 있다. 할당된 변수를 조작하려고 하면 저장된 실제 값이 조작된다.

  2. 참조타입 ( b, c, d )
    반면 객체와 같은 참조타입 데이터는 힙 영역에 저장된다. 참조타입 변수들도 원시타입과 마찬가지로 콜스택에 각각의 주소를 읽는데, 그 주소에 적혀있는 것은 배열이나 객체와 같은 데이터가 아니라 힙 영역의 주소들이 들어있다. 할당된 변수를 조작하려고 하면 참조하고 있는 주소가 가리키는 값이 조작된다.


let num1 = 123;
let num2 = num1;

num2 = 456;

console.log(num1);  // 123
console.log(num2);  // 456

const obj1 = { name: 'tom', age: 5 };
const obj2 = obj1;

obj2.name = 'jerry';

console.log(obj1);   // { name: 'jerry', age: 5 }
console.log(obj2);   // { name: 'jerry', age: 5 }

그림에서 확인할 수 있는 것처럼 원시형 데이터는 스택 메모리 만을 이용하고, 메모리에 값 자체를 저장한다. 그래서 원본 (num1)을 새로운 변수 (num2)에 할당하면 값을 복사하기 때문에 완전히 새로운 값이 만들어진다.

반면 참조형 데이터는 스택 외에도 힙을 사용하며 이를 스택에서 참조하는 방식으로 동작한다. 원본 (obj1)을 새로운 변수 (obj2)에 복사하면 스택에 담긴 힙 주소가 복사된다. 그래서 두 변수는 동일한 하나의 객체를 가리키게 되고, obj2를 수정했지만 obj1도 수정되었다.

  • 얕은 복사 (참조 복사) : 참조값(주소 값)을 복사
  • 깊은 복사 : 실제 값을 복사

✅ 전개 연산자

원시형 데이터와 달리 참조형 데이터는 얕은 복사가 발생하기 때문에 깊은 복사를 위한 별도의 처리가 필요하다. 반복문을 이용하는 방법이 있지만 ECMAScript6 에서 새로 추가된 문법 '전개 구문 (Spread Syntax)'을 사용하면 간단하게 깊은 복사를 할 수 있다.

우선, 전개 구문의 문법은 다음과 같다.

// 펼칠 대상이 객체인 경우
{...obj}
 
// 펼칠 대상이 배열인 경우
[...arr] or {...arr}

배열이나 객체 앞에 점 세 개를 붙여주면 된다. 단, 펼쳐진 객체나 배열을 담을 바구니가 필요한데 객체는 객체로, 배열은 객체나 배열로 담아낼 수 있다.

만약 펼친 배열을 중괄호로 감싸 객체로 만든다면, 각 요소는 프로퍼티 값이 되고 배열의 index가 프로퍼티 네임이 된다.

const myArray = ['a', 'b', 'c', 'd', 'e'];
 
const myObject = {...myArray};
 
console.log(myObject); // {0: "a", 1: "b", 2: "c", 3: "d", 4: "e"}

ES5 vs ES6 배열 문법

ES5 배열 내용 조합

ES5 에서는 배열의 내용을 합쳐 새로운 배열을 만들기 위해서 concat 메소드를 활용했다.

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
const arrWrap = arr1.concat(arr2, arr3);
 
console.log(arrWrap); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

concat 메소드로 새로운 배열을 만드는 것이 아닌, 기존 배열 요소에 값을 추가한다면 push 메소드를 사용할 것이다. arr1 배열에 arr2 배열 전체가 들어가 2차원 배열이 되었다. 이 경우 기존 자바스크립트에서는 배열 객체의 프로토타입 메소드인 push.apply를 사용해야 한다.

const arr1 = [1, 2, 3];
const arr2 = [4, 5];
arr1.push(arr2);
 
console.log(arr1); // [1, 2, 3, [4, 5]]
Array.prototype.push.apply(arr1, arr2);
 
console.log(arr1); // [1, 2, 3, 4, 5]

ES6 배열 내용 조합

전개 연산자를 활용하여 새로운 배열을 만들었다. concat 메소드를 사용한 코드보다 간결하다.
concat 메서드는 인자로 전달받은 값 순으로 기존 배열 끝에서부터 값을 추가하지만, 전개 연산자는 아래처럼 배열의 아무 곳에나 추가 할 수 있다는 장점이 있다.

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
const arrWrap1 = [...arr1, ...arr2, ...arr3];
 
console.log(arrWrap1); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

const arr = [4, 5, 6];
const arrWrap2 = [1, 2, 3, ...arr, 7, 8, 9]
 
console.log(arrWrap2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

push 메소드에 전개 연산자를 이용하면 2차원 형태가 되지 않는다.

const arr1 = [1, 2, 3];
const arr2 = [4, 5];
arr1.push(...arr2);
 
console.log(arr1); // [1, 2, 3, 4, 5]

하지만 2차원 이상의 배열을 할당할 땐 1차원 요소만 같은 1차원 레벨로 할당되고 2차원 이상의 배열은 그대로 들어간다.

const arr1 = [4, 5, [6, 7]];
const arr2 = [1, 2, 3, ...arr1];
console.log(arr2); // [1, 2, 3, 4, 5, [6, 7]]

전개 연산자를 이용한 복사

배열 복사

const Array1 = ['a', 'b', 'c', 'd', 'e'];
const Array2 = [...Array1]; 
 
console.log(Array1);    // ['a', 'b', 'c', 'd', 'e']
console.log(Array2);    // ['a', 'b', 'c', 'd', 'e']
 
console.log(Array1 === Array2);   // false

전개구문을 활용해 배열을 펼쳐 새로운 배열을 만들었다. 두 배열을 비교하면 false가 출력되면서 서로 다른 독립적인 배열이 되는 모습을 확인할 수 있다.

객체 복사

const myObject1 = {
    laptop: 'MacBook Pro',
    tablet: 'iPad Pro 11'
}
 
const myObject2 = {...myObject1};
 
console.log(myObject1); // {laptop: "MacBook Pro", tablet: "iPad Pro 11"}
console.log(myObject2); // {laptop: "MacBook Pro", tablet: "iPad Pro 11"}
 
console.log(myObject1 === myObject2); // false
// 서로 다른 주소 값을 가진 독립적인 객체임을 확인할 수 있다.

전개구문을 활용해 객체를 펼쳐 새로운 객체를 만들었다. 두 객체는 똑같은 모양의 프로퍼티를 갖지만, 두 객체를 비교할 경우 false가 출력되면서 서로 다른 주소 값을 가진 독립적인 객체임을 확인할 수 있다.

또한 전개 구문을 활용하면 이렇게 다른 객체의 프로퍼티를 복사해오면서 추가로 프로퍼티를 작성할 수도 있다.

const myObject1 = {
    laptop: 'MacBook',
    tablet: 'iPad'
}
 
const myObject2 = {
    ...myObject1,
    phone: 'Galaxy'
};
 
console.log(myObject1); // {laptop: "MacBook", tablet: "iPad"}
console.log(myObject2); // {laptop: "MacBook", tablet: "iPad", phone: "Galaxy"}

전개 연산자를 이용한 구조 분해 할당

배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 나머지 값들은 한데 모아서 저장하고 싶을 때가 있다. 이럴 때 전개 연산자 ... 을 추가하면 ‘나머지(rest)’ 요소를 가져올 수 있다.

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

// `rest`는 배열입니다.
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
profile
오늘도 신나개 🐶

0개의 댓글