객체는 데이터에 의미 를 부여할 수 있습니다.
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)를 이용해서 객체를 만듭니다.
let user = {
firstName : 'Sung Uk',
lastName : 'Han',
email : 'hansunguk6178@gmail.com',
city : 'Suwon'
}
위 객체의 값을 사용하는 방법은 두 가지가 있습니다.
user.firstName; // 'Sung Uk'
user.email; // 'hansunguk6178@gmail.com'
''
, ""
)로 감싸줘야 합니다. 백틱(`)도 사용 가능합니다.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
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
얕은 복사(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()
메서드를 통해 얕은 복사와 깊은 복사를 살펴보려 합니다.
sources
객체의 속성을 복사하여 target
객체에 할당한 후 반환합니다.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)
또한 같은 문제가 있습니다.
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 문자열로 반환하고 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