Home 자바스크립트의 Object
Post
Cancel

자바스크립트의 Object

원래는 부트캠프 막바지에 마스터님께서 퀴즈로 내어주신 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

잘 된 것 같지만 문제가 하나 있다

image

Human의 prototype을 Student prototype에 그대로 초기화 해주었기 때문에 생성자 함수가 그대로 Human(name)인 것을 확인할 수 있다. Student prototype의 생성자를 다시 Student로 돌려주자

1
Student.prototype.constructor = Student;

image

이번엔 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;

image

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에 바로 접근하게 코드를 수정했다고 생각하고 코드의 실행 과정을 살펴보자

  1. obj.a 라는 코드로 a에 접근하게 되어 get()을 호출한다
  2. console.log('get')을 호출한다
  3. this.a 는 obj.a 이므로 다시 get() 을 호출한다
  4. 1~3 무한 반복

그래서 obj.a에 바로 접근하면 해당 프로퍼티에 직접 접근하는 것이 아닌 내부에만 존재하는 것으로 취급하는 _a로 연결시키고 접근하고 수정하는 것이다


JavaScript : 프로토타입(prototype) 이해

프로토타입 메서드와 __proto__가 없는 객체

MDN, Object

This post is licensed under CC BY 4.0 by the author.