1. 객체 직렬화
- 객체 직렬화(serialize)는 객체를 문자열로 변환하는 작업이다.
- JSON.stringify() : 객체에서 문자열로 변환(열거 가능한 프로퍼티만 직렬화함)
- JSON.parse(): 문자열을 객체로 변환(date는 문자열 유지)
let obj = {x:1, y:{x: [false, null, ""]}};
let s = JSON.stringify(obj); // s == '{"x":1, "y":{"z":[false,null,""]}}'
let p = JSON.parse(s); // p == {x:1, y:{z: [false, null, ""]}}
2. 객체 메서드
- toString() 메서드 : 메서드의 인자가 없고, 호출되 객체의 값을 문자열로 반환
- toLocaleString() 메서드 : toString()과 기능은 같으나, 숫자, 날짜, 시간을 지역에 맞게 문자열로 변환
- valueOf() 메서드 : Numer()를 통해서 숫자로 변환할 때 호출
- toJson() 메서드 : Object.protoType에 정의된 메서드는 아니지만 JSON.stringify() 메서드는 직렬화할
객체에서 toJSON()메서드를 검색하여 반환값을 직렬화한다.
//toString, toLocaleString
let point = {
x: 1000,
y: 2000,
toString: function() { return `(${this.x}, ${this.y}`;},
toLocaleString: function() {
return `(${this.x.toLocaleString()}, ${this.y.toLocalString()})`;
}
};
point.toString() // --> "(1000, 2000)"
point.toLocaleString() // --> "(1,000, 2,000)": 천 단위 구분자 있음
// valueOf
let point = {
x: 3,
y: 4,
valueOf: function() { return Math.hypot(this.x, this.y); }
}
Number(point) // --> 5: 숫자로 변환할 때 valueOf()가 호출
point > 4 // --> true
point > 5 // --> false
point < 6 // --> true
// toJson
let point = {
x: 1,
y: 2,
toString: function() { return `(${this.x}, ${this.y})`},
toJson: function() { return this.toString(); }
}
3. 확장된 객체 리터럴 문법
- 단축 프로퍼티 : 변수와 프로퍼티의 이름이 동일할 경우 프로퍼터 이름 하나만 표현하면 된다.
ex) let x = 1, y = 2;
let = { x, y }
- computed property : 프로퍼티의 이름이 변수로 저장되어있을 때 객체를 만들고 프로퍼티를
추가하는 과정이 필요함
-프로퍼티 이름인 심벌 : computed property를 통해 심벌을 프로퍼티 이름으로 이용 가능
const PROPERTY_NAME = "p1";
function computedPropertyName() { return "p" + 2 }
let p = {
[PROPERTY_NAME]: 1,
[computedPropertyName]: 2
}
p.p1 + p.p2 // => 3
//Symbol 이용
const extension = Symbol("my extension symbol")
let obj = {
[extension]: { /* 이 객체에 확장 데이터를 저장합니다.*/}
}
obj[extension].x = 0;
- 분해 연산자 : ...을 사용해 기존 객체의 프로퍼티를 새 객체에 복사할 수 있음
1) 어떤 객체의 분해 연산자와, 그 객체의 동일한 프로퍼티 이름의 프로퍼티를 결합할 경우
마지막에 위치한 프로퍼티의 값만 남는다.
2) 상속된 객체는 분해 연산자에 포함되지 않는다.
3) 분해 연산자를 많은 횟수의 루프나 재귀함수에 쓰게되면 성능이 저하될 수 있다.
let position = {x:0, y:0};
let dimensions = {width: 100, height: 75}
let rect = {...position, ...diemnsions};
rect.x + rect.y + rect.width + rect.height // => 175
let obj = {x: 1}
let p = {x: 0, ...obj};
p.x // --> 1: 객체 obj의 값이 초기값을 덮어쓴다.
let q = {...0, x: 2};
q.x // --> 2: 2라는 값이 o의 기존 값을 덮어쓴다.
- 단축 메서드 : 객체 내에 프로퍼티 값이 메서드일때, 메서드명으로 프로퍼티명을 대체가능
let square = {
area() { return this.side * this.side;}
side: 10
};
square.area() // => 100
// Symbol, computed property 등 사용 가능
const METHOD_NAME = "m";
const Symbol = Symbol()
let = weirdMethods = {
"method With Spaces"(x) { return x + 1;},
[METHOD_NAME](x) { return x + 2;},
[symbol](x) { return x + 3; }
}
weirdMethods["method With Spaces"](1) // --> 2
weirdMethods[METHOD_NAME](1) // --> 3
weirdMethods[symbol](1) // --> 4
- 프로퍼티 게터와 세터 : 읽기와 쓰기를 가능하게 하는 프로퍼티
// 세터, 게터의 일반적인 사용
let obj = {
// 일반적인 데이터 프로퍼티
dataProp: value,
// 함수의 쌍으로 접의된 접근자 프로퍼티
get accessorProps() { return this.dataProp; },
set accessorProps(value) {this.dataProp = value; }
}
// 응용1
let obj = {
// x와 y는 일반적인 데이터 프로퍼티
x: 1.0,
y: 1.0,
// r은 게터와 세터가 있는, 읽고 쓸 수 있는 접근자 프로퍼티
get r() { return Math.hypot(this.x, this.y) },
set r(newValue) {
let oldValue = Math.hypot(this.x, this.y);
let ratio = newValue / oldValue;
this.x *= ratio;
this.y *= ratio;
}
//theta는 게터만 읽기 전용 접근자 프로퍼티임
get theta() { return Math.atan2(this.y, this.x); }
};
p.r // --> Math.SQRT2
p.theta // => Math.PI / 4
// 게터와 세터 프로퍼티 상속가능
let q = Object.create(p); //게터와 세터를 상속하는 새 객체
q.x = 3; q.y = 4; // q의 데이터 프로퍼티를 설정
q.r // --> 5: 상속된 접근자 프로퍼티 동작
q.theta // --> Math.atan2(4,3)
// 응용2 : 이 객체는 점점 증가하는 시리얼 번호를 만든다.
const serialnum = {
// 이 데이터 프로퍼티에 다음 시리얼 번호가 들어간다.
// 프로퍼티 이름에 있는 _는 이 프로퍼티를 내부에서만 사용한다는 의미
_n:0,
// 현재 값을 증가시켜 반환
get next() {return this._n++;},
// n에 새 값을 할당하지만 현재 값보다 커야한다.
set next(n) {
if (n > this._n) this._n = n;
else throw new Error("serial number can only be set to a larger value");
}
}
serialnum.next = 10; // 시리얼 번호 시작을 정한다.
serialnum.next // --> 10
serialnum.next // --> 11: 실행할 때마다 값이 달라진다.