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 에 나와있고

내가 알아볼 것은 접근자 서술자이다

접근자 서술자는 classsettergetter를 일반 객체에서도 적용시켜주는 역할을 한다고 보면 된다

먼저 익숙한 클래스의 settergetter를 살펴보자

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 의 접근 서술자도 마찬가지다 클래스가 아닌 일반 객체 내부의 값을 수정, 접근할 때

그 행동을 가로채서 내가 원하는 다른 로직을 끼워넣고 반환시킬 수 있게 해주는 getset을 설정할 수 있다

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라는 프로퍼티에 대해서 getset을 설정하는 코드이다

여기서 this._a 에 접근하는 코드에서 나는 헷갈림이 있었다 이해를 돕기위한 간단 설명을 해보자면

(thisobj를 가르킨다)

this.a에 바로 접근하게 코드를 수정했다고 생각하고 코드의 실행 과정을 살펴보자

  1. obj.a 라는 코드로 a에 접근하게 되어 get()을 호출한다
  2. console.log('get')을 호출한다
  3. this.aobj.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.