원래는 부트캠프 막바지에 마스터님께서 퀴즈로 내어주신 Object.create의 역할은 무엇인가요?
에 대해서 포스팅하려고 했는데 찾아보니 분량이 너무 적은 것 같아서 Object를 전반적으로 포스팅하려고 한다.
Object
객체 생성하기
자바스크립트에서 객체를 만드는 방법은 다음과 같다.
생성자를 사용한 생성 방법
const obj = new Object({a:1})
객체 리터럴을 이용한 생성 방법
const obj = {a:1}
두 방법 다 동일한 모양의 객체를 생성한다.
Object.create()
를 이용한 방법아래에서 좀 더 자세히 살펴보자
Object.assign()
복사하려는 객체의 모든 열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환합니다.
여기서 열거 가능한 자체 속성이란 for...in
으로 불러올 수 있는 프로퍼티 중 프로토타입 체인을 확인하지 않고 자신만의 직접적인 프로퍼티를 말한다.
1
2
3
4
5
6
7
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
//target : { a: 1, b: 4, c: 5 }
//returnedTarget : { a: 1, b: 4, c: 5 }
주의 사항
assign은 깊은 복사일까? 얕은 복사일까?
1
2
3
4
5
6
const obj1 = { a: 0 , b: { c: 0}};
const obj2 = Object.assign({}, obj1);
obj1.a = 3;
// obj1 { a: 3 , b: { c: 0}};
// obj2 { a: 0 , b: { c: 0}};
객체를 그대로 복사해와서 obj1.a
의 값을 변경했는데 obj2.a
의 값이 변경되지 않았다.
이 코드만 보면 assign은 깊은 복사인 것 같다. 다음 코드를 이어서 보자
1
2
3
4
obj1.b.c = 3;
// obj1 { a: 3 , b: { c: 3}};
// obj2 { a: 0 , b: { c: 3}};
만약 복사하는 프로퍼티가 객체에 대한 참조라면 참조값을 복사해오기 때문에 얕은 복사가된다.
다음과 같이 JSON 메서드를 사용해서 깊은 복사를 할 수 있다.
1
2
3
4
5
6
7
obj1 = { a: 0 , b: { c: 0}};
const obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
// obj1 { a: 4 , b: { c: 4}};
// obj3 { a: 0 , b: { c: 0}};
Object.create()
지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듭니다.
위에서 언급한 객체를 만드는 방법 중 하나이다.
1
2
3
4
5
6
7
8
9
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (상속됨)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
프로토타입 체인을 이용해서 상속된 객체를 생성할 수 있다.
순수 사전식 객체
1
2
const obj = Object.create(null);
// obj ---> null
이런식으로 프로토타입이 없는 객체를 생성할 수도 있는데 이를 ‘아주 단순한(very plain)’ 혹은 ‘순수 사전식(pure dictionary)’ 객체
라고 부르는데 Object
나 {...}(리터럴)
방식으로 생성한 객체보다 훨씬 단순하다.
이 순수 사전식 객체는 프로토타입으로 Object.prototype
를 가지고 있지 않기 때문에 toString()
과 같은 메서드를 사용할 수 없다.
Object.create(null)
을 사용하면 프로토타입이 없는 객체를 만들 수 있습니다. 이런 객체는"__proto__"
를 키로 사용해도 문제를 일으키지 않기 때문에 커스텀 사전을 만들 때 유용합니다.
라고 하는데.. 개발하면서 객체의 key값으로 __proto__
를 사용할 일이 있기나 할까 라는 의문이 든다.
상속
생성자 함수의 상속에 사용할 수도 있다.
사람을 나타내는 Human 과 자식인 Student 생성자들이 있다고 가정하자.
1
2
3
4
5
6
7
function Human(name) {
this.name = name;
}
Human.prototype.sayName = function() {
console.log(this.name);
};
Human은 sayName이라는 메서드를 가지고 있으며 인스턴스에 할당된 name을 출력한다.
이제 Student 생성자 함수를 만들어보자
1
2
3
function Student(name) {
Human.call(this, ...arguments);
}
Student의 prototype을 Human prototype을 가르키게해서 sayName을 사용할 수 있게 해주자
1
2
3
4
5
6
7
Student.prototype = Human.prototype;
const human = new Human('human');
const student = new Student('student');
human.sayName(); // human
student.sayName(); // student
잘 된 것 같지만 문제가 하나 있다
Human의 prototype을 Student prototype에 그대로 초기화 해주었기 때문에 생성자 함수가 그대로 Human(name)
인 것을 확인할 수 있다. Student prototype의 생성자를 다시 Student로 돌려주자
1
Student.prototype.constructor = Student;
이번엔 Human의 prototype 생성자 함수가 Student로 바뀌었다.. 이 지옥에서 벗어나기 위해서 사용하는 것이 Object.create()
이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Human(name) {
this.name = name;
}
Human.prototype.sayName = function() {
console.log(this.name);
};
function Student(name) {
Human.call(this, ...arguments);
}
Student.prototype = Object.create(Human.prototype);
Student.prototype.constructor = Student;
Object.defineProperty()
이하 예전 블로그 복붙
Object.defineProperty(객체, 프로퍼티, 설명)
정적 메서드는 객체에 직접 새로운 속성을 정의하거나 이미 존재하는 속성을 수정한 후, 그 객체를 반환한다
MDN에 나와있는 정의.
말이 어려운데 쉽게 설명하자면 객체
내부에 있는 프로퍼티
에 설명
을 따라서 해당하는 속성을 정의하거나 수정하고 다시 객체
를 반환하는 Object의 정적 메서드이다
1
2
3
4
5
6
7
const obj = {};
Object.defineProperty(obj, 'value', {
value:1000,
}); // 이 때 반환값은 obj 객체
console.log(obj); // {value: 1000}
defineProperty
에서 설명은 보다시피 객체로 서술되며 데이터 서술자와, 접근자 서술자 두 가지 종류로 나뉘게 된다
좀 더 자세한 설명들은 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty 에 나와있고
내가 알아볼 것은 접근자 서술자이다
접근자 서술자는 class
의 setter
와 getter
를 일반 객체에서도 적용시켜주는 역할을 한다고 보면 된다
먼저 익숙한 클래스의 setter
와 getter
를 살펴보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Obj {
constructor(a) {
this.a = a;
}
get getA(){
console.log('get');
return this.a;
}
set setA(nextValue){
console.log('set');
this.a = nextValue;
}
}
const obj = new Obj('test');
obj.getA; // obj.a => 'test'
// console log print 'get'
obj.setA = 'asdf'; // obj.a => 'asdf'
// console log print 'set'
클래스에서는 obj
내부의 값이 바뀔 때 무언가 로직을 추가해 줄 수 있다
defineProperty
의 접근 서술자도 마찬가지다 클래스가 아닌 일반 객체 내부의 값을 수정, 접근할 때
그 행동을 가로채서 내가 원하는 다른 로직을 끼워넣고 반환시킬 수 있게 해주는 get
과 set
을 설정할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = {};
Object.defineProperty(obj, 'a', {
get(){
console.log('get');
return this._a;
},
set(nextValue){
console.log('set');
this._a = nextValue;
}
});
obj.a // obj.a => undefined
// console log print 'get'
obj.a = 'test' // obj.a => test
// console log print 'set'
obj
객체의 a
라는 프로퍼티에 대해서 get
과 set
을 설정하는 코드이다
여기서 this._a
에 접근하는 코드에서 나는 헷갈림이 있었다 이해를 돕기위한 간단 설명을 해보자면
(this
는 obj
를 가르킨다)
this.a
에 바로 접근하게 코드를 수정했다고 생각하고 코드의 실행 과정을 살펴보자
obj.a
라는 코드로a
에 접근하게 되어get()
을 호출한다console.log('get')
을 호출한다this.a
는obj.a
이므로 다시get()
을 호출한다- 1~3 무한 반복
그래서 obj.a
에 바로 접근하면 해당 프로퍼티에 직접 접근하는 것이 아닌 내부에만 존재하는 것으로 취급하는 _a
로 연결시키고 접근하고 수정하는 것이다