객체 리터럴 { } 을 사용하는 방법 대신에, 규격만을 전달하여
유사한 객체 여러 개를 만드는 방법이 있습니다.
new 연산자와 생성자 함수를 이용하여 객체를 생성합니다.
생성자 함수는 일반 함수와 기술적으로 차이가 없으나, 사용할 때 다음과 같은 규칙을 따라야 합니다.
- 함수 이름의 첫 글자는 대문자로 시작
- 반드시
new연산자를 붙여 함수 실행
new 연산자를 사용하면 특별한 동작을 수행합니다.
앞에 new 연산자를 붙여 사용하면 대문자로 시작하지 않아도 모든 함수가 생성자 함수로 동작할 수 있습니다.
생성자 함수를 사용하는 예시입니다.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
new 연산자를 사용한 생성자 함수는 일반 함수와 달리 추가적인 동작이 수행됩니다.
생성자 함수를 실행했을 뿐인데 user 변수에 객체가 할당됨을 위 예시에서 확인할 수 있습니다.
코드에 나타나지 않는 실제 동작은 다음과 같다고 볼 수 있습니다.
function User(name) {
// this = {}; (빈 객체가 암시적으로 만들어짐)
// 새로운 프로퍼티를 this에 추가함
this.name = name;
this.isAdmin = false;
// return this; (this가 암시적으로 반환됨)
}
동작은 다음과 같습니다.
this 에 할당할 빈 객체 생성this 의 객체에 추가this 반환 따라서, user 변수는 this 가 가리키는 객체를 참조하게 됩니다.
생성자 함수에 return 문이 있다면 ❓
this 를 반환해야 하는 생성자 함수에 별도의 return 문이 존재한다면 어떻게 될까요?
결과는 반환값에 따라 달라집니다.
this 반환this 를 반환해야 하므로 생성자 함수에는 거의 return 문이 없습니다.
생성자 함수를 사용하여 프로퍼티에 메서드를 할당하는 것도 가능합니다.
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "제 이름은 " + this.name + "입니다." );
};
}
let bora = new User("이보라");
bora.sayHi(); // 제 이름은 이보라입니다.
객체의 프로퍼티 키에는 문자형과 심볼형 자료만이 올 수 있습니다.
Symbol 은 원시형 데이터로, 유일무이한 식별자를 만드는 데 사용됩니다.
let id = Symbol("id"); // id 라는 심볼 이름 => 심볼 설명
id 라는 심볼을 만들고 "id" 라고 설명을 붙였습니다.
설명 문자열은 없어도 됩니다.
심볼의 특징은 다음과 같습니다.
.toString() 메서드 사용심볼을 프로퍼티 키로 사용하면 외부 코드에서 접근할 수 없고 값도 덮어쓸 수 없는 프로퍼티를 만들 수 있습니다.
let age = Symbol("age");
let user = { // 서드파티 코드에서 가져온 객체
name: "John",
[age]: 20 // 심볼 변수 age 를 키로 사용
};
let id = Symbol("id");
user[id] = 1; // id 심볼을 키로 사용하여 값 1 할당
alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근
서로 이름이 같더라도 충돌하지 않는 식별자를 만들고 싶을 때 심볼을 활용합니다.
심볼은 특정한 경우에 배제됩니다.
for...in 반복문 : 키가 심볼인 프로퍼티는 그냥 넘어감Object.keys(obj) : 키가 심볼인 경우는 반환 ❌반면, Object.assign 의 경우 키가 심볼이어도 배제하지 않습니다.
심볼은 이름이 같더라도 별개로 취급되지만, 반대로 이름이 같으면 같은 개체로 취급하길 원할 수도 있습니다.
전역 심볼 레지스트리를 사용하여 심볼을 생성하고, 읽어오면 됩니다.
Symbol.for(key) 을 사용하여 전역 심볼을 생성하고 읽어옵니다.// 전역 레지스트리에서 심볼 읽기
let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼 생성
// 동일한 이름을 이용해 심볼을 다시 읽기
let idAgain = Symbol.for("id"); // 심볼이 존재하므로 그 값을 읽어옴
// 두 심볼은 동일
alert( id === idAgain ); // true
이름을 이용하여 전역 심볼을 생성하고 읽어옵니다.
Symbol.keyFor(symbol) 을 이용하여 심볼 이름 얻기// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
해당 심볼의 이름을 얻어옵니다.
객체로 연산을 수행할 때, 객체를 출력할 때 등의 경우 자동 형 변환이 일어납니다.
객체를 원시값으로 변환하여 연산을 수행하는 것이죠.
객체의 형 변환은 다음과 같이 적용됩니다.
Boolean 형 변환 : 무조건 true 반환toPrimitive, toString, valueOf 메서드를 객체에 구현하여 사용객체의 형 변환에는 hint 값을 사용합니다.
hint 는 변환을 기대하는 자료형, 목표로 하는 자료형으로 생각하면 됩니다.
숫자 연산 수행 시 hint 는 number, 출력할 때에는 string 이 됩니다.
그 외의 애매한 경우는 default 가 되며, number 와 동일하게 처리합니다.
내장 심볼을 이용한 toPrimitive 메서드를 구현하면 객체의 형 변환에 해당 메서드를 호출합니다.
Symbol.toPrimitive 는 반드시 원시형 자료를 반환해야 합니다.
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
// hint 가 string 이면 이름을, 그 외의 경우엔 money 를 반환
}
};
// 데모:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
메서드 하나로 모든 종류의 형 변환을 다룰 수 있습니다.
Symbol.toPrimitive 가 없는 경우 toString 이나 valueOf 메서드를 사용합니다.
toString 이나 valueOf 는 객체를 반환해도 에러가 나지 않습니다.
hint가string이면toString을 먼저 사용,toString이 없다면valueOf사용- 그 외의 경우에는
valueOf를 먼저 사용, 이후에toString사용
toString 과 valueOf 는 기본적으로 다음과 같은 동작을 수행합니다.
toString은 문자열[object Object]반환valueOf는 객체 자신을 반환
valueOf 는 객체 자신을 반환하므로 그 결과가 무시됩니다.
따로 정의하지 않은 경우에는 그냥 이 메서드가 존재하지 않는다고 생각하면 됩니다.
toString 과 valueOf 메서드를 구현해봅시다.
let user = {
name: "John",
money: 1000,
// hint가 "string"인 경우
toString() {
return `{name: "${this.name}"}`;
},
// hint가 "number"나 "default"인 경우
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
hint 에 맞게 각 메서드가 호출되는 것을 확인할 수 있습니다.