Part 2. Javascript 배열, 객체 Ch 2. 객체

HanSungUk·2022년 5월 11일
0

Javascript

목록 보기
7/16
post-thumbnail

Part 2. Javascript 배열, 객체 Ch 2. 객체 입니다.

현재 코드스테이츠 강의를 통해 프론트엔드를 학습하고 있습니다.
본 포스트는 해당 강의에 대한 내용 정리를 목적으로 합니다.

학습목표

  • 배열과 객체의 특징을 구분하고, 배열과 객체의 특징에 따라 웹 애플리케이션 개발 시 어떻게 사용되는지 이해한다.
  • 객체 속성(property)의 추가, 조회, 변경, 삭제를 자유자재로 할 수 있다.
  • 객체 속성 조회법 두 가지, dot notation과 bracket notation의 차이를 이해한다.
  • 배열과 객체, 반복문을 응용하여 능숙하게 대량의 정보를 다룰 수 있다.

1. 객체 기초

객체는 데이터에 의미 를 부여할 수 있습니다.

let userFisrName = 'Sung Uk';
let userLastName = 'Han';
let userEmail = 'hansunguk6178@gmail.com';
let city = 'Suwon';

위 변수들은 따로 선언되고 있어서 서로 어떠한 관계가 없습니다.
위 변수들을 하나의 연관된 의미로 나타낼 수 있는 방법이 없을까요?

let user = [
	'Sung Uk',
    'Han',
    'hansunguk6178@gmail.com',
    'Suwon'
]

첫 번째, 방법은 배열이 있습니다. 배열의 경우 각 값이 하나의 변수로 묶여있긴 하지만, 이 경우에는 각 index가 어떤 정보를 갖고 있는지 미리 알고 있어야 합니다. index로 접근할 경우 가독성도 떨어집니다.

가장 좋은 방법은 객체 입니다.

let user = {
  firstName : 'Sung Uk',
  lastName : 'Han',
  email : 'hansunguk6178@gmail.com',
  city : 'Suwon'
}

객체는 키와 값의 쌍(key-value pair)으로 이뤄져있습니다.
키, 값 사이는 콜론(:)으로 구분합니다.
키와 값의 쌍(key-value pair)은 쉼표(comma)로 구분합니다.
중괄호(curly bracket, braces)를 이용해서 객체를 만듭니다.

2. 객체 다루기

let user = {
  firstName : 'Sung Uk',
  lastName : 'Han',
  email : 'hansunguk6178@gmail.com',
  city : 'Suwon'
}

위 객체의 값을 사용하는 방법은 두 가지가 있습니다.

  1. Dot notation
user.firstName; // 'Sung Uk'
user.email; // 'hansunguk6178@gmail.com'
  1. Bracket notation
    bracket notation을 사용하여 값을 조회할 때는 key를 따옴표('', "")로 감싸줘야 합니다. 백틱(`)도 사용 가능합니다.
user[ 'firstName' ]; // 'Sung Uk'
user[ "city" ]; // 'suwon'
user[ `lastName` ]; // 'Han'

아래의 형태는 ReferenceError를 발생시킬 수 있습니다.
이 에러 메시지는 정의되지 않은(not defined) 변수를 참조(reference)할때 발생합니다.
이유는 lastName을 객체의 Key가 아니라 변수로 보고 있기 때문입니다.

let user = {
  firstName : 'Sung Uk',
  lastName : 'Han',
  email : 'hansunguk6178@gmail.com',
  city : 'Suwon'
}

user[lastName]; // ReferenceError: content is not defined

let keyname = 'lastName';
user[keyname] // 'Han'

Dot notation과 Bracket notation의 가장 큰 차이점은
bracket notaion은 변수를 활용해서 객체의 속성에 접근 가능하다는 점입니다.

let person = {
  name : 'Han',
  age : '10'
}

function getProperty(obj, propertyName){
	return obj[propertyName]
}

// 1
let output = getProperty(person, 'name');
console.log(output) // 'Han'

// 2
let output2 = getProperty(person, 'age');
console.log(output2) // '10'

위 두 가지 방법을 이용해 값을 추가할 수도 있습니다.

user.hobby = ['Golf', 'coding', 'badminton']
user['job'] = 'Front-end developer';
let user = {
  firstName : 'Sung Uk',
  lastName : 'Han',
  email : 'hansunguk6178@gmail.com',
  city : 'Suwon',
  hobby : ['Golf', 'coding'],
  job : 'Front-end developer'
}

delete 키워드를 이용해 삭제 도 가능합니다.

delete user.hobby = ['Golf', 'coding'];

in연산자를 이용해 해당하는 키가 있는지 확인할 수 있습니다.

'city' in user; // true
'age' in user; // false

3. 메서드와 this

 const person = {
   name: "han",
   gender: "male",
   getPersonData: function () {
     return [this.name, this.gender]
   };
   // 여기서 this는 person 객체를 가리킵니다.

method는 '어떤 객체의 속성으로 정의된 함수'를 말합니다.
위의 person 객체를 예로 든다면, getPersonData는 person 객체의 속성으로 정의된 함수인 메소드 라고 할 수 있습니다. person.getPersonData()와 같은 형태로 사용(호출)할 수 있습니다.

사실은, 전역 변수에 선언한 함수도 웹 페이지에서 window 객체의 속성으로 정의된 함수라고 할 수 있습니다.
window. 접두사 없이도 참조가 가능하기 때문에(window.getProperty()라고 사용해도 됩니다.), 생략하고 쓰는 것 뿐입니다.
이렇듯, method는 항상 '어떤 객체'의 method입니다.

따라서 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this입니다. 메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있습니다.

let person = {
  name: "han",
  age: "30",
  getPersonData: function () {
    return [this.name, this.age]
  }
}
console.log(person.getPersonData()); // ["han", "30"]

this를 사용하지 않고 외부 변수를 참조해 객체에 접근할 수도 있지만 외부 변수가 다른 변수로 할당되면 원치 않는 문제가 발생할 수 있습니다.

let person = {
  name: "han",
  age: "30",
  getPersonData: function () {
    return [person.name, person.age]
  }
}

let user = person;
person = null;

console.log(user.getPersonData());
// Error: Cannot read property 'name' of null

getPersonData 함수의 반환값을 person.name, person.age 대신 this.name, this.age로 변경한다면 문제를 해결할 수 있습니다.

let person = {
  name: "han",
  age: "30",
  getPersonData: function () {
    return [this.name, this.age]
  }
}

let user = person;
person = null;
console.log(user.getPersonData());
// ["han", "30"]
console.log(person.getPersonData());
// Error: Cannot read property 'name' of null

4. 얕은 복사와 깊은 복사

얕은 복사(shallow copy)는 기존 메모리 heap 영역의 주소값을 참조하여, 같은 데이터를 사용합니다.

const obj = {value: 2};
const newObj = obj;

newObj.vaule = 2;

console.log(obj.value); // 2
console.log(obj === newObj); // true

깊은 복사(deep copy)는 새로운 메모리 heap 영역에 복사한 데이터를 할당하여 새로운 주소값을 사용합니다.

Object.assign() 메서드와 전개 연산자, Array.slice() 메서드 그리고 JSON.stringify(), JSON.parse() 메서드를 통해 얕은 복사와 깊은 복사를 살펴보려 합니다.

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(source);
// expected output: Object { b: 4, c: 5 }
const obj = {a : 1};
const newObj = Object.assign({}, obj);

newObj.a = 2;

console.log(obj); // {a : 1}
console.log(obj === newObj) // false

위 예제에서처럼 Object.assign() 깊은 복사처럼 보이지만
아래 예제처럼 중첩된 구조에 대한 깊은 복사는 이뤄지지 않습니다.

const obj = {
	a: 1,
    b: {
    	c: 2,
    },
};

const newObj = Object.assign({}, obj);

newObj.b.c = 3;

console.log(obj); // {a: 1, b: {c:3}};
console.log(obj === newObj) // false
console.log(obj.b.c === newObj.b.c) // true

겉으로 보기에는 깊은 복사처럼 보이지만 복사된 2차원 객체는 얕은 복사가 된 것을 확인할 수 있습니다. 다음에 언급할 Array.slice()전개 연산자(spead operator) 또한 같은 문제가 있습니다.

  • Array.slice()
    slice() 메서드는 어떤 배열의 begin부터 end까지(end 미포함)에 대한 얕은 복사본을 새로운 배열 객체로 반환합니다. 원본 배열은 변경되지 않습니다.(immutable)
const arr = [1, 2, 3];
const copiedArr = arr.slice()

copiedArr.push(4)
console.log(arr); // [1, 2, 3]
console.log(copiedArr); // [1, 2, 3, 4]

위 예제에서처럼 Array.slice()가 깊은 복사처럼 보이지만
아래 예제처럼 중첩된 구조에 대한 깊은 복사는 이뤄지지 않습니다.

const arr = [1, 2, [3,4]];
const copiedArr = arr.slice()

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

copiedArr.pop(0)
console.log(arr); // [1, 2, 3]
console.log(copiedArr); // [1, 2]

위 예제에서처럼 전개 연산자(spread operator)가 깊은 복사처럼 보이지만 아래 예제처럼 중첩된 구조에 대한 깊은 복사는 이뤄지지 않습니다.

const arr = [1, 2, [3,4]];
const copiedArr =[...arr]

copiedArr[2].pop(0);
console.log(arr); // [1, 2, [3]]
console.log(copiedArr); //  [1, 2, [3]]

그렇다면 중첩된 구조에 대한 깊은 복사까지 이뤄지려면 어떤 메서드를 사용해야할까요?
바로 JSON.stringify(), JSON.parse() 메서드입니다.

  • JSON.stringify()
  • JSON.parse()
    JSON.stringify()메서드는 인수를 JSON 문자열로 반환하고 JSON.parse()메서드는 인수를 객체로 반환합니다.
    깊은 복사를 위해서는 두 개의 메서드는 함께 사용해야 합니다.
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const newObj = JSON.parse(JSON.stringify(obj));

newObj.b.c = 3;

console.log(obj); // { a: 1, b: { c: 2 } }
console.log(obj.b.c === newObj.b.c); // false

0개의 댓글