JavaScript 데이터

JaeHwan Kim·2021년 4월 8일

HTML, CSS, JS

목록 보기
4/4
post-thumbnail

업데이트 21.04.09

데이터

리터럴 복습

원래 변수나 배열 등 JS의 데이터는 new라는 키워드로 js에서 제공하는 각각의 데이터에 맞는 전역 객체 별로 데이터를 생성해줘야 한다.

매번 복잡하게 생성하다 보니 생산성이 떨어졌고, 이를 위해 JS의 리터럴 표기법(방식)이 등장했다.

예시1) "", '', `` 문자열의 리터럴 방식
예시2) {} 객체의 리터럴 방식
예시3) [] 배열의 리터럴 방식

indexOf() 메소드

indexOf() 메소드는 호출한 String 객체에서 주어진 값과 일치하는 첫 번째 인덱스 값을 반환한다. 일치하는 값이 없으면 -1을 반환한다.

const paragraph = "it's baobab blog";
const serachTerm = "baobab";
// indexOf() 메소드와 연산자를 적절히 섞어 문장에 특정 문자열이 포함되어 있는 지 확인할 수 있다.
// 예) const str = paragraph.indexOf(searchTerm === -1);
const indexOfFirst = paragraph.indexOf(searchTerm);

console.log(indexOfFirst);

length 메소드

length 메소드는 변수나 특정 문자열 등 데이터의 글자 수를 반환한다.

const str = '0123';
console.log(str.length);      // 4

slice() 메소드

slice(n1, n2)일 때, slide 메소드는 데이터를 n1부터 n2-1만큼 잘라낸 뒤 반환한다.

const str = "Hello world!"; 
console.log(str.slice(0, 3));   // Hell이 아니라 Hel까지만 잘려서 출력된다.
// 따라서, 필요한 문자열들을 자르기 위해 length 메소드와 indexOf() 메소드를 적절하게 사용해야 한다.

replace() 메소드

replace('test', 'TEST')일 때, test 문자열을 TEST로 바꾸어 반환한다.

const str = "Hello world!";
console.log(str.replace('world', 'WORLD')); // Hello WORLD! 출력
console.log(str.replace(' world!', '')); //  WORLD! 문자열이 삭제된다.

match() 메소드

match() 메서드는 문자열이 정규식과 매치되는 부분을 반환한다.

const str = "baobab@test.com";
// 정규표현식을 사용해야 한다.
// @를 기준으로 .(첫 글자)부터 +(@을 만나기 전까지)를 나눈다. > 나누면 배열로 저장된다.
// 그 중 0번 인덱스를 콘솔에 출력한다.
console.log(str.match(/.+(?=@)/)[0]);  // baobab

trim() 메소드

trim() 메소드는 앞, 뒤 모든 공백을 삭제한 데이터 값을 반환한다.

const str = '         Hello world!    ';
console.log(str.trim());    // Hello world! 출력

숫자(number) 데이터

toFiexed(), parseInt(), parseFloat() 메소드

1) toFiexed() 는 소수점을 자르는 용도로 사용되는 메소드이다.
2) parseInt() 는 데이터를 정수형으로 변환하는 메소드이다.
3) parseFloat() 는 데이터를 실수형으로 변환하는 메소드이다.
*typeof는 데이터의 형이 무엇인지 반환해 주는 메소드이다.

const pi = 3.14159265358979;
const str = pi.toFixed(2);    // 소수점 둘째자리까지 자른 뒤 '문자열'로 반환
console.log(typeof pi);    // string 출력

const integer = parseInt(str);  // 정수형 숫자 데이터로 변환
const float = parseFloat(str);  // 실수형 숫자 데이터로 변환
console.log(integer);   // 3 출력
console.log(float);     // 3.14 출력
console.log(typeof integer, typeof float); // number number 출력

Math 내장 객체 메소드

수학과 관련된 다양한 메소드들이 Math 객체에 포함되어 있다.
클래스 안에 있는 각종 메소드들을 통해 효율적으로 데이터를 가공할 수 있다.

console.log('abs: ', Math.abs(-12));    // 절댓값 12 출력
console.log('min: ', Math.min(2, 8));   // 실인수 중 제일 작은 값 2 출력
console.log('max: ', Math.max(2, 8));   // 실인수 중 제일 큰 값 8 출력
console.log('ceil: ', Math.ceil(3.14)); // 데이터의 올림 값을 출력 3.14 => 4
console.log('floor: ', Math.floor(3.14)); // 데이터의 내림 값을 출력 3 => 3
console.log('round: ', Math.round(3.14)); // 데이터의 반올림 값을 출력 3.14 => 3
console.log('round: ', Math.round(3.54)); // 데이터의 반올림 값을 출력 3.54 => 4
console.log('random: ', Math.random()); // 랜덤한 숫자를 반환한다. 0.1039801850 ... 

배열 데이터

배열은 데이터들의 집합이며, 아래와 같이 배열을 선언할 수 있다.
또한, 아래의 예시들을 참고하여 배열에서 사용할 수 있는 메소드들을 익히는 것이 좋다.

  const tempNum =  [1,2,3,4];
  console.log(tempNum[1]);      // 2 출력
  // 1은 Index Number라고 지칭하며, 이를 이용하여 데이터 값을 불러오는 것을 Indexing 이라고 한다.
  // 1,2,3,4를 Elements 또는 items 이라고 부른다.

  console.log(tempNum.length); // 4 출력
  console.log([].length); // 0 출력 -> 이를 이용하여 배열 값이 비어있는 지 확인할 수 있다.

.concat() 메소드

concat() 메소드는 배열과 배열을 합쳐주는 메소드이다.

  const tempNum =  [1,2,3,4];
  const tempNum2 =  [5,6,7,8];
  console.log(tempNum.concat(tempNum2)); // 모든 배열(tempNum+tempNum2)의 items가 출력
  // 단, 새로운 배열로 생성되는 것이지, tempNum이나 tempNum2에 영향을 주지 않는다.

.forEach(), .map() 메소드

forEach() 메소드는 배열 데이터의 크기(items의 개수)만큼 콜백 함수를 실행해 준다.
그러나, 반환 값이 없어 변수에 저장하여 활용할 수 없다.
map() 메소드는 배열 데이터의 크기(items의 개수)만큼 콜백 함수를 실행해 준다.
단, 반환 값이 있다는 특징이 있어 변수에 저장하여 활용할 수 있다.
=> map() 메소드는 기존 배열에는 영향을 주지 않는다.

const fruits = ['apple','grape','lemon'];

fruits.forEach(function(fruit, i) {
  console.log(fruit, i);
})
// Apple, Banana, Cherry 출력

const b = fruits.map(function (fruit, index) {
  return `${fruit}-${index}`    // 배열의 items로 반환하여 b의 index에 저장된다.
}) 
console.log(b)
// 3개의 items을 가진 새로운 배열이 출력된다.

// map과 객체의 리터럴을 이용하면 아래와 유용하게 사용할 수 있다.
// 또한, 일반 함수를 화살표 함수로 치환해서 사용할 수 있다.
// function 생략 가능하나 인수가 2개여서 ()는 생략 불가하다.
// return 외에 다른 로직이 없기 때문에 생략 가능하다.
// 단, 객체 데이터를 리턴할 때에는 ()로 묶어줘야 한다.
const c = fruits.map((fruit, index) => ({
  id:index, 
  name: fruit
  }))
console.log(c)
// 3개의 items를 가진 배열이 생성되고, 안에는 객체 3개가 들어있다.

.filter() 메소드

filter() 메소드는 인수로 '콜백 함수'를 불러온다.
이때, 로직을 조건식으로 짜놓으면 조건에 부응하지 못하는 애들을 삭제한 채로 값을 반환한다.
=> 정확히는 true인 애들만 배열에 저장되는 것이다.
=> filter() 메소드도 map() 메소드와 마찬가지로 기존 배열에는 영향을 주지 않는다.

const numbers = [1,2,3,4];

const a = numbers.map(number => number < 3);
console.log(a);

const b = numbers.filter(number => number < 3);
console.log(b);

.find() .findIndex() 메소드

find() 메소드는 조건에 맞는 특정 item을 찾아 반환한다.

const fruits = ['Apple', 'Banana', 'Cheery'];

  // B로 시작하는 단어를 찾아 반환한다.
const a = fruits.find(fruit => /^B/.test(fruit));
console.log(a);    // Banana 출력

findIndex() 메소드는 조건에 맞는 특정 item를 찾아 해당 인덱스 번호를 반환한다.

const fruits = ['Apple', 'Banana', 'Cheery'];

const a = fruits.findIndex(fruit => /^B/.test(fruit));
console.log(a);   // 1 출력

.includes() 메소드

includes() 메소드는 해당 배열의 includes로 전달받은 인수가 포함되었는 지 Boolean 형으로 반환한다.

const fruits = ['Apple', 'Banana', 'Cheery'];

const a = fruits.includes('Apple')
const b = fruits.includes('Apple2')
console.log(a)    // True
console.log(b)    // False

.push() .unshift() 메소드

push() 메소드는 배열의 마지막 인덱스에 값을 추가하는 것이고,
unshift() 메소드는 배열의 첫 인덱스에 값을 추가한다.
=> 즉, 원본 배열 값에 변화를 준다.

const fruits = ['Apple', 'Banana', 'Cheery'];

fruits.push('Orange');
console.log(fruits[3]);  // Orange

fruits.unshift('Grape');
console.log(fruits[0]);   // Grape

.reverse() 메소드

reverse() 메소드는 배열 item의 순서를 뒤집어버린다.

const fruits = ['Apple', 'Banana', 'Cheery'];

fruits.reverse();
console.log(fruits);  // 배열의 item이 Cherry, Banana, Apple 순으로 출력된다.

.splice() 메소드

splice() 메소드는 특정 위치(인덱스) 값부터 특정 값만큼 잘라낸다.
=> 원본 배열의 값에 영향을 준다.

const fruits = ['Apple', 'Banana', 'Cheery'];

  // 0번 인덱스를 기준으로 2개의 인덱스를 자른다.
  // 0 : Apple, 1 : Banana
  // 맨 마지막 인수는 0번 인덱스 기준으로 Test라는 문자열이 배열에 추가된다.
  // 인수에 따라 추가되는 문자열 위치가 달라진다.
fruits.splice(0,2, 'Test');     
console.log(fruits);    // Test, Cherry

객체(Object) 메소드

전역 객체에서 사용할 수 있는 메소드와 일반 객체와 사용할 수 있는 메소드가 다르다.
이에 전역 객체에서 주로 사용하는 메소드들은 익혀놓는 것이 좋다.
이때, 전역 객체(Object)에서만 사용할 수 있는 메소드들을 정적(Static) 메소드라고 한다.

.assign() 메소드

assign() 메소드는 target, source 인수를 받아와서 새로운 객체를 생성하거나, 기존 객체를 합치는 용도로 사용한다.

const userAge = {
  // key: value
  // property: value
  name: 'baobab',
  age: 21
}

const userEmail = {
  name: 'baobab',
  email: 'baobab@baobab.live'
}

// userEmail 객체를 복사하여 userAge에 저장한다.
// 헷갈릴 수 있지만, target 객체, userAge 객체는 따로 저장된 것이다.
const target = Object.assign(userAge, userEmail);
console.log(target);
console.log(userAge);
// 값이 같다고 무조건 일치한 것은 아니다.
// 같은 메모리 공간을 참조하고 있어야 일치한 것이다.
console.log(target === userAge);

// a, b 객체는 따로 생성했기 때문에, 당연히 서로 다른 메모리 공간을 차지하고 있다.
// 따라서 두 객체는 일치하지 않는다.
const a = { k: 123 };
const b = { k: 123 };
console.log(a === b);

// 원본 데이터를 변경하지 않고, 두 객체를 합치고 싶으면 아래와 같이 빈 객체를 Target으로 넣으면 된다.
// 또한, userAge의 객체를 새로운 객체로 옮긴 후 변수에 저장하면 메모리 공간을 분리할 수 있다.
const newTarget = Object.assign({}, userAge, userEmail);
const newTargetEmpty = Object.assign({}, userAge);
console.log(newTarget);
console.log(newTargetEmpty);

// 메모리 공간이 분리 된 객체들이기 때문에 false 출력
console.log(newTargetEmpty === userAge)

.keys() 메소드

keys() 메소드는 인수로 받은 객체의 keys(properties)을 반환한다.

const User = {
  name: "baobab",
  age: 21,
  email: "baobab@baobab.live"
};

// 인수로 받은 객체의 keys(properties)을 반환한다.
const keys = Object.keys(User);
// ['name', 'age', 'email'] 반환
console.log(keys);

// keys(properties) 값으로도 객체의 값에 접근할 수 있다.
console.log(User['email']);

// keys와 map을 같이 사용하면 정말 유용하다.
// keys의 반환 값을 변수로 저장한 뒤 저장된 변수로 map 메소드를 사용하면 된다.
// map 메소드를 통해 User의 keys(properties) 값만큼 반복한 뒤 배열로 반환한다.
const values = keys.map(key => User[key]);
console.log(values);

구조 분해 할당

객체나 배열의 필요한 부분만 할당하는 것을 구조 분해 할당이라고 한다.
이는, 객체의 속성이 많을 때 유용하게 사용된다.

구조 분해 할당 시 객체와 배열의 차이점은?
객체는 순서에 상관 없이 객체의 속성을 기준으로 할당을 할 수 있지만,
배열은 반드시 순서를 고려하여 배열의 요소를 할당해야 한다.

// 객체의 구조 분해 할당 (Destructuring Assignment) (비구조화 할당)
const User = {
  name: 'Baobab',
  age: 21
}

const { name, age = 21, email: address = "No Email" } = User
// E.g, user.address와 동일하다.
// User의 email 객체를 address 변수로 저장할 수 있다.
// User의 email 객체가 없을 경우 기본 값을 지정할 수 있다. 

console.log(`사용자의 이름은 ${name} 입니다.`);
console.log(`${name}의 나이는 ${age}세입니다.`);
console.log(`${name}의 이메일 주소는 ${address} 입니다.`);

// 배열의 구조 분해 할당 (Destructuring Assignment) (비구조화 할당)
const fruits = ['Apple', 'Banana', 'Cherry'];

const [a,b,c,d = "Default"] = fruits;
// index 3번에 값이 없을 경우 Default 출력

// 만약 Cherry 요소만 불러오고 싶으면 아래와 같이 구조 분해 할당하면 된다.
const [, , fruit] = fruits;

console.log(a,b,c,d); // Apple Banana Cherry Default

console.log(fruit); // Cherry

전개 연산자 (Spread, ...)

전개 연산자란 쉼표를 기준으로 배열의 요소들이 전개되어 사용할 수 있도록 도와주는 연산자이다.

// 아래와 같이 전개 연산자를 사용하면 배열의 요소들을 불러올 수 있다.
const fruits = ['Apple', 'Banana', 'Cherry', 'Orange'];
console.log(...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: "Cherry"} 출력

// 화살표 함수를 이용하면 위 함수를 축약해서 사용할 수 있다.
// 매개 변수보다 배열의 요소가 더 많다면 전개 연산자를 이용하여 매개 변수에 할당해야 한다.
// 이러한 매개변수를 나머지 매개 변수(Rest Parameter)라 부른다.
const toObjectArrow = (a,b,...c) => ({a, b, c})
console.log(toObjectArrow(...fruits));
// {a: "Apple", b: "Banana", c: Array(2)} 출력
// c: Array(2) 안에는 Cherry와 Orange가 들어있다.

데이터 불변성

JS의 데이터는 원시 데이터와 참조형 데이터로 나눌 수 있다.
원시 데이터의 불변성을 제대로 이해하면 참조형 데이터의 가변성도 이해하기 수월하다.
원시 데이터의 종류로는 String, Number, Boolean, undefined, null 이 있다.

아래는 원시 데이터의 불변성 정리 내용이다.

let a = 1
let b = 4
console.log(a, b, a === b) // a의 메모리 공간과 b의 메모리 공간이 다르기 때문에 false 반환
b = a // a의 메모리 주소를 b에 할당하는 것이다. 즉, 같은 값이 출력될 뿐만 아니라 같은 메모리 주소를 가리키고 있는 것이다.
console.log(a, b, a === b) // a, b가 가리키는 메모리 주소가 같아 true 반환
a = 7 // a의 7이 할당되면서 새로운 메모리 주소의 7이 저장
console.log(a, b, a === b)  // 이에 서로 가리키는 메모리 주소가 달라 false 반환
let c = 1 // c에 1를 할당
console.log(b, c, b === c)  // 원시 데이터는 값을 기준으로 메모리 공간을 차지하고 있다. 
// 즉, b와 c는 1를 보유하고 있는 메모리 주소를 가리키고 있는 것이기 때문에 true 반환

아래는 참조형 데이터의 가변성 정리 내용이다.

let a = { k: 1 }
// 참조형 데이터는 할당할 때마다 새로운 메모리 공간을 사용한다.
// 원시 데이터는 값이 같은 경우 같은 메모리 공간을 사용한다.
let b = { k: 1 }
console.log(a, b, a === b)
// 두 객체의 값이 같다하더라도 각 객체가 다른 메모리 주소를 가리키기 때문에 false가 출력된다.
// 즉, 원시 데이터와는 다르게 참조형 데이터는 데이터의 불변성이 만족되지 않는다. (가변성)
a.k = 7
// a의 k 속성 값을 7로 변경한다.
b = a
// b는 a와 동일한 메모리 주소를 가리킨다.3
// 즉 b의 k 속성은 7이 된다. 
// 이전에 b가 가리키고 있던 b의 k 속성 1인 메모리 공간은 가비지 컬렉터에 의해 자동으로 삭제된다. 
// (메모리 공간 낭비가 발생하지 않기 위함이다.)
console.log(a,b, a=== b)

얕은 복사와 깊은 복사

이전 예제는 모두 얕은 복사(Shallow Copy)를 이용한 결과였다.
얕은 복사로 인해 개발자가 의도하지 않은 결과가 나올 수 있다.
즉, 객체를 따로 구분하고 싶으나 참조하는 메모리 공간이 같아 값을 공유하게 된다.
이를 해결할 수 있는 방법이 바로 깊은 복사(Deep Copy)이다.

import _ from 'lodash'

const User = {
  name: 'baobab',
  age: 21,
  emails: ['baobab@baobab.live']
}

// 0. 얕은 복사의 예시) 
const copyUser0 = User
const copyUser1 = Object.assign({}, User) 
// 1. 위와 같이 전역 객체의 assign 메소드를 이용하여 빈 리터럴(객체)를 만들어준 후 User를 옮겨 닮을 수 있다.
// 이때, 옮겨담는 과정에서 새로운 객체가 들어온 것으로 판단하기 때문에 새로운 메모리 공간을 차지하게 되고,
// 객체 별로 데이터를 담을 수 있게 된다.

const copyUser2 = {...User};
// 2. 위와 같이 전개 연산자를 이용하여 User 안에 담아져 있는 속성을 옮겨 담을 수 있다.
// 이때, 옮겨담는 과정에서 새로운 객체가 들어온 것으로 판단하기 때문에 새로운 메모리 공간을 차지하게 되고,
// 객체 별로 데이터를 담을 수 있게 된다.

const copyUser3 = _.cloneDeep(User);
// 3. lodash의 cloneDeep 메소드를 이용하여 깊은 복사가 가능하다.
// User의 객체 속성을 재귀적으로 복사해 온다.
console.log(copyUser0 === User)   // 같은 메모리 공간을 참조하고 있기 때문에 True
console.log(copyUser1 === User)   // 각 개별의 메모리 공간을 차지하고 있기 때문에 False
console.log(copyUser2 === User)   // 각 개별의 메모리 공간을 차지하고 있기 때문에 False
console.log(copyUser3 === User)   // 각 개별의 메모리 공간을 차지하고 있기 때문에 False

깊은 복사를 했다고 무조건 객체의 모든 속성이 서로 다른 것은 아니다.
만약, 다른 객체 간의 동일한 속성, 값이 같은 메모리 공간을 참조하는 경우가 생길 수 있다.

const User = {
  name: 'baobab',
  emails: ['iwantbaobab@gmail.com', 'iwantbaobab@baobab.live']
}

const copyUser = {...User};
const copyUser2 = _.cloneDeep(User);

console.log(copyUser === User)    
// 객체가 참조하고 있는 메모리는 분리되어 있어 false 출력

console.log(copyUser.emails === User.emails)  
// 그러나, 객체 안의 속성 값은 동일한 메모리 주소를 참조하고 있어 true 출력한다. 
// lodash의 cloneDeep 메소드와는 달리 '객체'만 새롭게 할당했기 때문에 당연히 false 출력이 정상적이다. '객체' 안의 '배열' 속성은 새로운 메모리 공간에 할당되지 않았기 때문이다.

console.log(copyUser2.emails === User.emails)  
// lodash의 cloneDeep 메소드는 재귀적으로 복사해오기 때문에 객체 안에 있는 속성의 값까지 다 새로운 메모리 공간에 할당할 수 있다.
profile
바오밥 나무가 될테야

0개의 댓글