(ES6) - 08 Object 오브젝트

  • SUNGMIN SHIN
  • 40 Minutes
  • 2017년 10월 13일

[ ECMA Script6 ]

(http://book.naver.com/bookdb/book_detail.nhn?bid=11556385)


8.1 오퍼레이션


ES6에서 Object 오브젝트에 프로퍼티를 작성하고 제어하는 방법이 추가되었으며 ES5와 다르게 변경된 것도 있습니다.

Object에 같은 Key 사용

Object의 key값이 같은 프로퍼티 두 개를 작성했을 때 ES버전별로 차이가 존재 합니다.
ES3: key값이 같더라도 추가
ES5: strict 모드에서 에러 발생
ES6: 모드 관계 없이 나중에 작성된 프로퍼티 값으로 대체

1
2
let sameKey = {one: 1, one: 2};
console.log(sameKey.one);

변수 이름으로 값 설정

변수 이름을 사용하여 Object 프로퍼티 값으로 설정가능

1
2
3
4
5
let one = 1;
let two = 2;
let values = {one, two};

console.log(values);

Object에 function 작성

Object에 함수를 작성하는 방식은 ES5와 ES6가 차이를 가집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ES5
let es5 = {
getTotal: function(param){
return param + 123;
}
};

// ES6
let es6 = {
getTotal(param) {
return param + 123;
}
};

console.log(es5.getTotal(100));
console.log(es6.getTotal(100));


8.2 디스크립터(Descriptor)


디스크립터는 ES5에서 제시되었으며 ES6에서 이를 바탕으로 여러 기능이 추가 되었습니다.

1
2
3
4
Object.defineProperty({}, 'book' ,{
value: 10,
enumerable: true
});

참고: https://msdn.microsoft.com/ko-kr/library/dd548687(v=vs.94).aspx

프로퍼티 디스크립터

상세 내용은 표8-1(P95) 참고
데이터 타입의 value, writable 그리고 액세스 타입의 get, set속성은 함께 작성할 수 없으며 프로퍼티 디스크립터에서는 타입을 별도로 작성하지 않고 속성으로 데이터 타입과 액세스 타입을 구분합니다 (즉 타입이 기본 값을 가지고 있으므로 별도의 선언이 없다면 기본 값으로 사용)


8.3 get, set 속성


get 속성은 getter의 기능을 set 속성을 setter의 기능을 제공합니다.

get 속성

1
2
3
4
5
6
7
let obj = {};
Object.defineProperty(obj, 'book', {
get: function() {
return '책'
}
});
console.log(obj.book);

1> obj에서 book프로퍼티 작성 여부를 체크
2> get속성 여부를 체크
3> get 속성 값인 함수를 실행
의 순서로 실행되며 getter는 함수를 호출하는 형태 obj.book()이 아닌 obj.book으로 함수 이름만 작성합니다.

set 속성

1
2
3
4
5
6
7
8
9
let obj = {};
Object.defineProperty(obj, 'set', {
set: function(param) {
this.result = param;
}
});
obj.set = 200;

console.log(obj.result);

1> obj에서 set프로퍼티 작성 여부를 체크
2> set 속성 여부를 체크
3> set 속성 값인 함수를 실행
의 순서로 실행되며 setter는 함수를 호출하는 형태 obj.set(100)이 아닌 obj.set = 100의 형태로 작성합니다.
여기서 getter로 사용되는 obj.result는 get 속성을 작성하지 않았지만 디폴트 getter가 호출되어 obj의 result프로퍼티의 값을 반환합니다.


8.4 getter(ES6)


ES6에서의 getter는 함수(메서드) 이름 앞에 명시적으로 get을 작성하며 function키워드를 생략하고 아래와 같이 작성합니다.

1
2
3
4
5
6
7
8
let obj = {
value: 123,
get getValue(){
return this.value;
}
};

console.log(obj.getValue);


8.5 setter(ES6)


ES6에서의 setter는 함수(메서드) 이름 앞에 명시적으로 set을 작성하며 function키워드를 생략하고 아래와 같이 작성합니다.

1
2
3
4
5
6
7
8
9
let obj = {
value: 123,
set setValue(value){
this.value = value;
}
};

obj.setValue = 200;
console.log(obj.value);


8.6 is(): 값과 값 타입 비교


두 개의 파라미터 값과 값 타입을 비교하여 같으면 true를 아니면 false를 반환합니다.
하지만 값과 값 타입을 비교하는 것이지 오브젝트를 비교하는 것은 아니며 그렇기 때문에 Array와 Array를 비교하거나 Object와 Object를 비교하는 경우 false가 반환 됩니다.

== (값만 비교)
=== (값과 값의 타입을 모두 비교)
Object.is() (값과 값의 타입을 모두 비교 0의 음수, 양수여부 그리고 NaN을 비교할 수 있음)

1
2
3
4
5
6
7
8
9
10
11
console.log('1:', Object.is(1, '1'));
console.log('2:', Object.is(NaN, NaN), NaN === NaN);

console.log('3:', Object.is(0, -0), 0 === -0);
console.log('4:', Object.is(-0, 0), -0 === 0);

console.log('5:', Object.is(-0, -0), -0 === -0);
console.log('6:', Object.is(NaN, 0/0), NaN === 0/0);

console.log('7:', Object.is(null, null), null === null);
console.log('8:', Object.is(undefined, null), undefined === null);


8.7 assing(): 오브젝트 프로퍼티 복사


두 번째 파라미터의 오브젝트 프로퍼티를 첫 번 째 파라미터의 오브젝트 끝에 복사하고 첫 번째 파라미터를 반환 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
let obj = Object.assign(null, {
x: 1
});
} catch(e) {
console.log('null 지정 불가')
}

// 첫 번째 프로퍼티에 Number를 넣는 경우
console.log(Object.assign(123));
console.log(Object.assign(123) == 123);
console.log(Object.is(Object.assign(123), 123));

// 첫 번째, 두 번째 프로퍼티에 Number를 넣는 경우
console.log(Object.assign(456, 70));

첫 번째 파라미터에 열거 가능한 오브젝트가 아닌 Boolean, Number, String, Symbol을 지정하는 경우 값 타입의 오브젝트(primitiveValue)를 생성하며 지정하지 않거나 undefined와 null을 지정하면 에러가 발생 합니다.
(즉 Boolean, Number, String, Symbol가 첫 번째 파라미터에 지정되는 경우 파라미터의 값을 primitiveValue에 지정하고 해당 오브젝트를 생성되며 이렇게 생성된 오브젝트의 primitiveValue는 valueOf() 그리고 Symbol.toPrimitive로 확인할 수 있습니다.)
두 번째 이후 파라미터에는 열거 가능한 오브젝트가 아니라면 복사되지 않습니다.
프로퍼티에 복사는 own 프로퍼티로 한정되어 prototype과 프로퍼티 디스크립터는 복사되지 않습니다.

참고: primitiveValue (http://webclub.tistory.com/240)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj2 = Object.assign('ABC', {one: 1});
// String 오브젝트는 이터러블 오브젝트이므로 나열되어 출력
console.log(obj2);
// primitiveValue를 연산하는 경우
console.log(obj2 + 'DEF');

// Symbol 타입으로 생성되며 primitiveValue는 'ABC'가 됨
console.log(Object.assign(Symbol('ABC'), {one: 1}));

// 양쪽 파라미터 모두에 문자열은 사용할 수 없음
try {
let obj = Object.assign('ABC', 'ONE');
} catch(e) {
console.log('파라미터 모두 문자열 사용 불가');
}
1
2
3
4
5
6
7
8
let oneObj = {};
Object.assign(oneObj, 'ABC', undefined, null);
// Object.assign('ABC', undefined, null)과 동일
console.log(oneObj);

let twoObj = {};
Object.assign(twoObj, {key1: undefined, key2: null});
console.log(twoObj);

oneObj와 같이 첫 번째 파라미터에 오브젝트를 지정하고 두 번째 문자열 파라미터를 작성하면 ‘ABC’가 primitiveValue로 복사되지만 그 다음 파라미터들은 복사되지 않습니다.
twoObj처럼 파라미터가 아닌 Object의 프로퍼티 값으로 undefined나 null인 경우에는 에러없이 복사됩니다.


8.8 assign()의 필요성


1
2
3
4
5
6
7
8
9
10
11
let sports = {
event: '축구',
player: 11
};

let dup1 = sports;
let dup2 = Object.assign({}, sports);

console.log(dup1.event, dup2.event);
sports.event = '농구';
console.log(dup1.event, dup2.event);

위의 예제와 같이 빈 Object를 첫 번째로 인자로 사용하여 오브젝트의 프로퍼티를 복사하면 프로퍼티의 값이 연동되지 않습니다.


8.9 assign() 고려사항


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let oneObj = {one:1};
let twoObj = {two:2};
let mergeObj = Object.assign(oneObj, twoObj);

/*
mergeObj
1> mergeObj에서 Object.assign이 실행
2> oneObj에 twoObj의 프로퍼티를 복사
3> 복사한 값을 oneObj에 할당
4> 복사한 값을 반환

즉 반환 값이 첫 번째 파라미터에 복사한 값을 할당된 후 반환되므로 연동을 피하려면 빈 Object를 첫번 째 값으로 넣어야 합니다.
*/

console.log(Object.is(oneObj, mergeObj));

mergeObj.one = 456;
console.log(Object.is(oneObj, mergeObj));

위 예제와 같이 첫 번째 파라미터에 oneObj에 복사한 값을 할당 후 반환하므로 값이 연동되게 됩니다.
그리고 Object.assign({}, {one:1}, {two:2}, {two:3}) 과 같이 다수의 Object를 작성할 수 있으며 이때 왼쪽에서 오른쪽을 순서로 프로퍼티를 복사하게 되어 중복되는 키값은 대체되게 됩니다.


8.10 assign() getter


프로퍼티를 복사할 때 프로퍼티가 getter라면 함수가 아닌 return 값을 복사하게 됩니다(return 값이 없는 경우 undefined를 반환)

1
2
3
4
5
6
7
8
9
10
11
12
let count = {
current: 1,
get getCount() {
return ++this.current;
}
};

let mergeObj = {};
Object.assign(mergeObj, count);

// 여기서 count의 값이 1인 이유는 복사하는 시점의 값을 가져오기 때문
console.log(mergeObj);


8.11 setPrototypeOf


Object.setPrototypeOf( 오브젝트나 인스턴스, prototype이나 오브젝트나, null)

setPrototypeOf는 null이나 또다른 객체에 특정 객체 프로토타입을 설정할 수 있는 메서드로 첫 번째 파라미터에 오브젝트나 인스턴스를 지정하며 만약 결과가 false(즉 오브젝트에 프로퍼티를 추가, 확장할 수 없다면)라면 에러가 발생됩니다.
Boolean, Number, String, Symbol을 지정하는 경우 오브젝트를 생성하여 반환하지만 이 때 두 번째 파라미터를 생성한 오브젝트에 반영하지 않습니다.

두 번째 파라미터에는 오브젝트나 null을 지정하며 prototype이 아닌 function이나 인스턴스를 지정해도 에러가 발생하진 않지만 목적에 적합하지 않습니다.

참고: setPrototypeOf (https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let Sports = function(){};
Sports.prototype.getCount = function() {
return 123;
};

// Sports의 prototype을 protoObj에 복사되어 protoObj.__proto__.getCount가 존재하게 됨
let protoObj1 = Object.setPrototypeOf({}, Sports.prototype);
console.log(protoObj1.getCount());

// 두 번째 파라미터가 prototype이 아닌 변수 자체를 지정하는 경우 직접 호출할 수 없음
let protoObj2 = Object.setPrototypeOf({}, Sports);
try {
console.log(protoObj2.getCount());
} catch(e) {
console.log('에러', protoObj2.prototype.getCount.call(Sports));
}


8.12 proto


proto에 new 연산자로 생성한 인스턴스 또는 다른 오브젝트의 prototype에 연결된 프로퍼티가 첨부 됩니다.

인스턴스를 생성하면 오브젝트의 prototype에 연결된 프로퍼티가 인스턴스의 proto에 첨부됩니다. 이 환경이 만들어지면 prototype에 연결된 메서드를 인스턴스 메서드로 호출할 수 있게 됩니다. (여기서 proto에 첨부된다는 의미는 복사가 아닌 (메모리 주소 값의)참조)

prototype과 proto의 차이

proto에 존재하는 메서드는 object.name()의 형태로 직접 호출할 수 있지만 prototype에 연결된 메서드는 object.prototype.name.call()의 형태로 호출해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
let Sports = function() {
this.member = 11;
};
Sports.prototype.getMember = function() {};

let sportsObj = new Sports();
/*
1> Sports의 member가 sportsObj의 인스턴스 프로퍼티로 첨부
2> new 연산자로 생성자 함수를 호출하면 엔진이 __proto__를 생성하고 prototype에 연결된 프로퍼티를 첨부
3> 이렇게 연결된 prototype은 메모리의 주소를 참조하므로 시스템적으로 동일
*/

console.log(sportsObj.__proto__ === Sports.prototype);
1
2
3
4
5
6
7
8
9
10
11
12
13
let Sports = function(){};
Sports.prototype.get = function() {};

let sportsObj = new Sports();

// sportsObj의 __proto__에 메서드를 추가했지만 실제로는 Sports.prototype에 추가 됨
// 이는 Sports로 생성한 다른 인스턴스에서 추가한 set() 메서드를 공유하기 때문(콘솔 확인)
sportsObj.__proto__['set'] = function() {};
sportsObj.set();

let result = new Sports();
console.log(result.set);
console.log(sportsObj);


요약


오퍼레이션

  • ES6에서는 같은 키 값을 중복하여 할당하더라도 에러가 발생하지 않으며 나중에 작성된 프로퍼티 값으로 대체
  • 변수의 이름을 사용하여 Object의 프로퍼티 값을 설정할 수 있다
  • Object에 function을 작성 시 function 키워드를 작성하지 않는 형태로 변경

get, set 속성

  • get 속성은 getter의 기능을 set속성은 setter의 기능을 제공
  • 함수의 앞에 get, set 키워드를 작성하여 사용(ES6도 동일, 단 function 키워드는 작성하지 않음)

is()

  • Object.is()는 값과 값의 타입을 비교(오브젝트를 비교하는 것이 아님)
  • ‘===’ 기본 적으로 동일하지만 0의 음수, 양수 그리고 NaN 비교할 수 있다.

assign()

  • Object.assign()은 오브젝트 프로퍼티를 복사하는 메서드
  • 이 때 prototype과 프로퍼티 디스크립터는 복사되지 않음. (own 프로퍼티만 복사)
  • 첫 번째 파라미터에 Boolean, Number, String, Symbol을 지정하면 값 타입의 오브젝트를 생성하고 primitiveValue에 값을 할당
  • assign 메서드를 활용하면 프로퍼티 값의 연동을 제어할 수 있음
  • getter 프로퍼티를 복사하는 경우 반환된 값을 복사(return문이 없다면 undefined를 반환)

setPrototypeOf()

  • Object.setPrototypeOf는 null이나 또다른 객체에 특정 객체 프로토타입을 설정할 수 있는 메서드
  • 첫 번째 파라미터에 오브젝트나 인스턴스를 지정하며 만약 결과가 false(즉 오브젝트에 프로퍼티를 추가, 확장할 수 없다면)라면 에러가 발생
  • 첫 번째 파라미터에 Boolean, Number, String, Symbol을 지정하는 경우 오브젝트를 생성하여 반환하지만 이 때 두 번째 파라미터를 생성한 오브젝트에 반영하지 않음
  • 두 번째 파라미터에는 오브젝트나 null을 지정, prototype이 아닌 function이나 인스턴스를 지정해도 에러가 발생하진 않지만 목적에 적합하지 않음

8.12 proto

  • proto에 new 연산자로 생성한 인스턴스 또는 다른 오브젝트의 prototype에 연결된 프로퍼티가 첨부 됨
  • 인스턴스를 생성하면 오브젝트의 prototype에 연결된 프로퍼티가 인스턴스의 proto에 첨부(참조할 수 있게)되며 prototype에 연결된 메서드를 인스턴스 메서드로 호출할 수 있게 됨
  • proto에 첨부된 prototype은 메모리 주소 값을 참조하는 형태
  • proto에 존재하는 메서드는 object.name()의 형태로 직접 호출할 수 있지만 prototype에 연결된 메서드는 object.prototype.name.call()의 형태로 호출해야 함
  • 인스턴스에 proto에 값을 추가하더라도 생성자 함수에 prototype이 추가 됨