내가 보기에 JS 에서 객체가 갖는 의미는 상당히 크다. 거의 근본 이라고 할 수 있다.
JS는 객체에서 시작해서 객체로 끝난다고 생각할 정도로 그 영향력이 막강하다. 최근 JS Study를 통해 객체에 대한 여러 학습을 진행했으나, 여러 지식이 섞여 있어 이를 한번 명확히 정리해야 할 필요성을 느꼈다. 특히나 열거 가능한 속성에 대해 학습할때는 대체 열거 가능하다는 게 어떤 의미인지 이해하지 못해 학습에 시간이 꽤나 걸렸던 전적이 있어, 이번 기회에 한번 객체를 정리해보고자 한다.
{}
를 이용해 만들거나, 객체 생성자 new Object()
를 활용해 만들 수 있다.let obj = new Object(); // 객체 생성자 문법
let obj2 = {}; // 객체 리터럴 문법
key
는 오직 문자열 혹은 심볼형만 가능하며, value
는 모든 자료형이 허용된다.key
로 오면 자동으로 문자열로 형 변환 된다.key
에는 특별한 제약이 없다. JS에서 사용하는 예약어 또한 사용이 가능하다.let person = {
firstName: 'Baik', // key: 'firstName', value: 'Baik'
lastName: 'Gwangin', // key: 'lastName', value: 'Gwangin'
0: 'Zero', // key: "0", value: "Zero" (number를 string으로 자동 변환)
return: 'my future', // key: 'return', value: 'my future' (return 예약어를 key로 사용)
};
.
혹은 대괄호 연산을 사용한다..
의 경우에는 반드시 JS에서 유효한 식별자만 사용해야 한다.let person = {
'first Name': 'Baik', // key: 'first Name', value: 'Baik'
'last Name': 'Gwangin', // key: 'lastName', value: 'Gwangin'
};
person.first Name = 'Kim' // (X) 공백이 있어 유효한 식별자가 아니기에 에러 발생)
person['first Name'] = 'Kim' // (O) 정상적으로 프로퍼티 값이 Kim 으로 수정됨.
key
가 대괄호로 감싸진 경우를 계산된 (computed) 프로퍼티 라 한다.const firstName = 'first Name';
const last = 'last';
// computed property 에 의해 각 변수의 값이 프로퍼티 key를 구성하게 됨.
let person = {
[firstName]: 'Baik', // key: 'first Name', value: 'Baik'
[last + 'Name']: 'Gwangin', // key: 'lastName', value: 'Gwangin'
};
undefined
를 반환한다.in
키워드를 사용하여 프로퍼티의 존재 여부를 파악하자.let person = {
firstName: 'Baik',
lastName: 'Gwangin',
};
console.log('major' in person); // false
console.log('firstName' in person); // true
let test = {
"4": "4",
"2": "2",
test: "test",
"3": "3",
"1": "1",
test2: "test2",
};
for (let prop in test ) {
console.log(prop); // 1, 2, 3, 4, test, test2
}
Heap
에 객체 인스턴스를 생성하고, 저장된 메모리 주소를 Stack
에 기록하여 사용한다.Stack
메모리 주소를 가리킨다.Heap
의 경우 동적으로 메모리가 할당되는 메모리 영역이기에, 수시로 변동될 수 있는 객체를 저장하기에는 적합하다.const obj1 = {
name: 'Baik'
};
const obj2 = {
name: 'Baik'
};
console.log(obj1 === obj2); // false
Heap
에 저장된 객체 인스턴스의 메모리 주소를 비교하기 때문이다.const obj1 = {
name: 'Baik'
};
obj1 = null;
obj1
이 참조했던 객체 인스턴스는 obj1
이 null을 참조하는 순간 접근할 수 없게 된다.let obj1 = {
name: 'Baik'
};
let obj2 = {
name: 'Baik'
};
obj2 = obj1
console.log(obj1 === obj2); // true
obj2.name = 'Kim'
console.log(obj1.name) // Kim
obj2
가 obj1
이 가리키는 메모리 주소를 참조하도록 코드를 작성했다.obj2
식별자를 통해 객체를 수정할 경우, obj1
식별자로 객체에 접근하면 변경된 결과값을 얻게 된다.lodash
라이브러리에서 제공하는 cloneDeep()
메서드를 사용하는 방법이 있다.깊은 복사는 객체를 JSON으로 변환했다가 다시 객체로 재변환하는 방식으로 가능하지 않을까?
const baseObj = {
map: new Map(),
}
const copied = JSON.parse(JSON.stringify(baseObj));
// {map: {…}}, 겉으로는 잘 복사된 것처럼 보이나 map에 저장된 값은 일반 Object 이다.
console.log(copied);
Object
, Array
, Number
, String
, Boolean
, Null
타입만 가질 수 있다.value
와 함께 flag
라고 불리는 속성 세 가지를 함께 가진다.flag
의 값은 오직 boolean
만 올 수 있으며, 값에 따라 프로퍼티의 성질이 달라진다.true
라면 해당 프로퍼티의 값을 수정할 수 있다. 그렇지 않을 경우 해당 프로퍼티 값을 읽기만 가능하 도록 한다.true
라면 for...in
반복문을 사용하여 속성을 나열하도록 한다. 그렇지 않으면 반복에 포함되지 않는다.true
라면 해당 프로퍼티의 삭제나 플래그 값 수정을 허용한다. 아니면 삭제나 플래그 수정을 허용하지 않는다.configurable 플래그를 false 로 지정할 경우, 다시 true 로 되돌릴 방법은 없다.
configurable
플래그는 프로퍼티 플래그의 수정을 원천적으로 차단하기 때문이다.false
로 변환했다면, 다시 true
로 변환시키는 작업을 차단시킨다.Object.defineProperty
메서드를 사용하여 해당 프로퍼티를 재정의하는 작업도 차단된다.let user = { };
Object.defineProperty(user, "name", {
value: "John",
writable: true,
enumerable: true,
configurable: false,
});
Object.defineProperty(user, "name", {enumerable: false}); // TypeError: Cannot redefine property: name
user.name = 'Leon'; // writable flag 가 true 이므로 프로퍼티 값을 수정하는 것은 가능하다.
writable
속성의 값을 true
에서 false
로 변경하는 것은 된다. 반대의 경우는 불가능하다.configurable
플래그가 false
더라도 writable
이 true
라면 프로퍼티 값을 수정할 수 있다.열거 가능한 속성은 프로퍼티의 enumarable flag 의 값이 true 임을 의미한다.
enumerable
flag의 값이 true
일 경우에만 해당 프로퍼티는 for...in
반복문에 포함된다.Object.toString()
메서드도 열거 가능한 속성만을 포함하므로 enumerable
플래그를 체크한다.Object.getOwnPropertyDescriptor()
메서드를 사용하여 특정 프로퍼티의 정보를 얻을 수 있다.Object.getOwnPropertyDescriptors()
메서드를 사용하면 모든 프로퍼티 정보를 가져올 수 있다.let person = {
firstName: 'Baik',
lastName: 'Gwangin',
};
Object.defineProperty(person, 'firstName', {
value: 'Baik',
writable: false,
});
// {value: 'Baik', writable: false, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(person, 'firstName');
// firstName : {value: 'Baik', writable: false, enumerable: true, configurable: true}
// lastName : {value: 'Gwangin', writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors(person);
Object.defineProperty()
메서드를 사용하여 특정 프로퍼티의 플래그 값을 변경할 수 있다.Object.defineProperties()
메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있다.for...in
과 같은 반복문과 달리, 열거 불가능한 속성 (심볼형 포함) 들도 가져올 수 있다.let person = {
firstName: 'Baik',
lastName: 'Gwangin',
};
Object.defineProperty(person, 'firstName', { value: 'Kim', writable: 'false' });
Object.defineProperties(person, { firstName: { writable: 'false' }, lastName: { enumerable: 'true' } });
true
를 가진다.Object.defineProperty
메서드로 프로퍼티를 추가할 경우, 플래그 값은 false
를 가진다.let person = {
firstName: 'Baik',
lastName: 'Gwangin',
};
### let human = {};
Object.defineProperty(human, 'firstName', { value: 'Kim', writable: 'true' });
Object.defineProperty(human, 'lastName', { value: 'Chulsu', enumerable: 'true' });
// firstName : {value: 'Baik', writable: true, enumerable: true, configurable: true}
// lastName : {value: 'Gwangin', writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors(person);
// firstName : {value: 'Kim', writable: true, enumerable: false, configurable: false}
// lastName : {value: 'Chulsu', writable: false, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptors(human);