즉, 객체는 상태를 나타내는 프로퍼티와 상태(프로퍼티)를 참조하고 조작하는 메서드로 이루어진 집합체다.
클래스 기반 객체지향 언어는 클래스를 정의하고 필요한 시점에 new 연산자와 함께 생성자를 호출하여 인스턴스를 생성하는 방식으로 객체를 생성한다.
자바스크립트는 프로토타입 기반의 객체지향 언어로 다양한 방식으로 객체를 생성한다. 물론 클래스 방식으로의 생성 또 한 가능하다.
JS에서의 객체 생성 방법
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스 (따로 정리한 객체지향 프로그래밍 참고)
프로퍼티에는 마침표와 대괄호 접근 연산자로 접근 가능하다.
var person = {
name: "kim",
};
console.log(person.name); // kim
console.log(person["name"]); // kim
console.log(person[name]); // name is not defined
주의해야 할 점은 대괄호 연산자로 접근시 key를 ''로 감싼 문자열이여야 한다.
''가 없다면 name을 식별자(평가대상)으로 판단하여 찾지 못한다.
(key가 숫자인 경우는 '' 생략 가능하다.)
var person = {
name: "kim",
};
person.name = "Lee";
person.age = "30";
console.log(person);
// { name: 'Lee', age: '30' }
delete person.age;
console.log(person);
// { name: 'Lee' }
let x = 1;
let y = 2;
let obj = {
x: x,
y: y,
};
console.log(obj); // { x: 1, y: 2 }
let obj2 = { x, y };
console.log(obj2); // { x: 1, y: 2 }
프로퍼티 값으로 변수를 사용하는 경우 변수 이름과 프로퍼티 key가 동일한 이름일 때 프로퍼티 key를 생략할 수 있다. key는 변수 이름으로 생성된다.
const ex = "ex";
let i = 0;
const obj = {
[`${ex}-${++i}`]: i,
[`${ex}-${++i}`]: i,
[`${ex}-${++i}`]: i,
};
console.log(obj);
// { 'ex-1': 1, 'ex-2': 2, 'ex-3': 3 }
변수에 변수를 할당했을 때 무엇이 어떻게 전달 될까?
var score = 80;
var copy = score;
console.log(score) // 80
console.log(copy) // 80 <- 값에 의한 전달
위처럼 변수에 변수를 할당하면 원시값 80이 전달 된다.
이를 값에 의한 전달이라고 한다.
다만, 값 80은 다른 메모리 공간에 저장된 별개의 값이다.
score = 100;
console.log(copy) // ? // 80 이다.
값에 의한 전달 후 score의 값을 변경해도 copy의 값은 변경되지 않는다.
최초 전달 때 별개의 저장공간에 값이 저장되었기 때문이다.
이처럼 "값에 의한 전달"은 사실 값을 전달하는 것이 아닌 메모리 주소를 전달 한다고 할 수 있다.
- 80이 저장된 메모리 주소를 전달
- score는 재할당으로 100이 저장된 메모리 주소를 참조 (원시값은 불변이기 때문에)
- copy는 전달 받은 80이 저장된 메모리 주소를 계속 참조
하지만 객체는 원시값과는 다르게 mutable(변경가능) 하다.
- 원시 값을 갖는 변수는 값을 변경하려면 재할당 해야한다.
- 객체를 갖는 변수는 재할당 없이 객체를 변경 할 수 있다.
- 이는 메모리의 효율성이 높아진다.
- 하지만 여러개의 식별자가 하나의 객체를 공유 하는 부작용이 있다.(참조 주소가 같으니깐)
객체의 mutable한 이유로 객체를 복사하는 경우 얕은 복사와 깊은 복사로 나뉜다.
객체를 프로퍼티로 갖는 객체의 경우 한 단계(1차원)까지만 복사
: 중접된 객체는 참조 값만 복사해온다.
객체에 중접되어 있는 객체(다차원)까지 모두 복사
: 중접된 객체까지 원시값처럼 복사한다.
const obj = { x: { y: 1 } };
const obj1 = { ...obj }; // 얕은 복사
console.log(obj1 === obj); // false
console.log(obj1.x === obj.x); // true
// lodash의 cloneDeep을 사용한 깊은 복사
// "npm install lodash"로 lodash 설치 후 실행
const _ = require("lodash");
const obj2 = _.cloneDeep(obj); // 깊은 복사
console.log(obj2 === obj); // false
console.log(obj2.x === obj.x); // false
즉, 원시값을 할당한 변수를 다른 변수에 할당하면 깊은 복사
객체를 할당한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 할 수 있다.
- 일반적으로 사용하는 Object.assign, ...Object는 1차원에서는 깊은 복사지만 2차원 이상 부터는 얕은 복사다.
- 함수도 객체이기에 동일하게 적용된다.
- 깊은 복사를 하기 위해서는 lodash같은 기능을 가져와야한다.
타겟 객체에 소스 객체를 복사하여 붙여 넣는다.
Object.assign(타겟객체, 소스객체)
어떤 객체를 복사하여 새로운 객체를 만들때 사용한다.
const obj1 = { 1: 1, 2: 2, 3: 3 };
const newObj = Object.assign({}, obj1);
console.log(newObj); // { '1': 1, '2': 2, '3': 3 }
const obj2 = { 1: 1, 4: 1 };
const newObj2 = Object.assign({ obj1, obj2 });
console.log(newObj2);
// { obj1: { '1': 1, '2': 2, '3': 3 }, obj2: { '1': 1, '4': 1 } }
객체의 [key, value] 쌍의 배열을 반환한다.
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]
// array like object
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]
// non-object argument will be coerced to an object
console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]
// Or, using array extras
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }
객체를 불변성으로 만들어 준다.
동결된 객체는 속성 추가, 제거, 변경이 불가해진다.
const obj = {
prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// Expected output: 42
[key, value] 쌍의 목록을 객체로 만든다. ex) 배열 -> 객체
Object.entries()의 반대 역할
const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }
const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }
const object1 = { a: 1, b: 2, c: 3 };
const object2 = Object.fromEntries(
Object.entries(object1)
.map(([ key, val ]) => [ key, val * 2 ])
);
console.log(object2);
// { a: 2, b: 4, c: 6 }
객체의 속성을 배열로 반환한다.
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr));
// [ '0', '1', '2', 'length' ]
var obj = { 0: "a", 1: "b", 2: "c" };
console.log(
Object.getOwnPropertyNames(obj).forEach((v) =>
console.log(v + " -> " + obj[v])
)
);
/*
0 -> a
1 -> b
2 -> c
*/
객체의 속성값들을 배열로 반환한다.
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
// 유사 배열 (숫자를 속성으로 사용하는 객체)
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj)); // ['a', 'b', 'c']
// 유사 배열의 경의 속성으로 사용한 숫자의 크기 순으로 정렬되어 반환됩니다.
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']
// getFoo는 열거 가능한 속성이 아니라서 배열에 포함되지 않습니다.
var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 'bar';
console.log(Object.values(my_obj)); // ['bar']
// 객체가 아닌 경우에는 객체로 강제로 변환되어 적용됩니다.
console.log(Object.values('foo')); // ['f', 'o', 'o']
객체의 key값들을 배열로 반환한다.
// 단순 배열
const arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// 배열형 객체
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// 키와 순서가 무작위인 배열형 객체
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']
// getFoo 는 열거할 수 없는 속성입니다.
const myObj = Object.create({}, {
getFoo: {
value: function () { return this.foo; }
}
});