객체 생성하기
프로토타입 상속
접근자 프로퍼티
프로퍼티의 속성
프로퍼티가 있는지 확인하기
객체의 열거
객체 잠그기
Mixin
JSON
ES6부터 추가된 함수 기능
자바스크립트의 객체는 key, value의 한 쌍으로 묶은 집합.
객체는 객체 리터럴로 생성, 생성자를 통해 생성, Object.create 로 생성하는 방법이 있다
// 객체 리터럴로 생성하는 방법
var card = {name:"A", property: "1"};
// new + 생성자 이용
function Card(suit, rank) {
this.suit = suit;
this.rank = rank;
}
var card = new Card("heart", "A");
// Object.create() 이용
var card = Object.create(Object.prototype, {
suit: {
value: "하트",
writable: true,
enumerable: true,
configurable: true
},
rank: {
value: "A",
writable: true,
enumerable: true,
configurable: true
}
});
따라서 인스턴스를 여러개 생성하면 같은 작업을 하는 메서드를 생성한 인스턴스 숫자만큼 생성하게 되어 메모리에 낭비가 되고, 성능저하로 이어진다.
function Circle(center, radius) {
this.center = center;
this.radius = radius;
this.area = function () {
return Math.PI * this.radius * this.radius;
};
}
var c1 = new Circle({x:0, y:0}, 2.0);
var c2 = new Circle({x:0, y:1}, 3.0);
var c3 = new Circle({x:1, y:0}, 1.0);
prototype 객체에 메서드를 정의하는 방식으로 해결할 수 있다.prototype 객체가 프로퍼티로 정의되어 있다. prototype 프로퍼티의 기본값은 빈 객체의 참조이다.prototype 객체의 프로퍼티는 생성자로 생성한 모든 인스턴스가 함께 공유한다. (자신의 프로퍼티처럼 사용할 수 있다.)앞서 언급했던 문제를 prototype 의 메서드로 정의하는 방식을 통해 해결하는 모습을 보자.
function Circle(center, radius) {
this.center = center;
this.radius = radius;
}
// Circle 함수의 prototype프로퍼티 (프로토타입 객체) 에 메서드 추가
Circle.prototype.area = function () {
return Math.PI * this.radius * this.radius;
};
// Circle 생성자로 인스턴스 생성
var c1 = new Cricle({x:0, y:0}, 3.0);
var c2 = new Cricle({x:0, y:1}, 2.0);
var c3 = new Cricle({x:1, y:0}, 1.0);
c1, c2, c3 에는 area() 메서드가 존재하지 않지만, area() 메서드를 사용할 수 있다. 메서드의 this 또한 해당 인스턴스를 가리킨다.자바스크립트는 프로토타입 상속에 기반을 둔 객체 지향언어 이다.
모든 객체는 내부 프로퍼티로 [[prototype]] 을 갖고 있다. 이는 prototype 프로퍼티와는 다른 객체이다. 기존에는 읽고 쓰는것이 불가능하였지만, ES6 부터는 __proto__ vmfhvjxldp [[prototype]] 의 값이 저장된다. (모든 브라우저가 지원하는 것은 아니다.)
var obj = {};
console.log(obj.__proto__); // -> Object {}
객체의 __proto__ 프로퍼티는 그 객체에게 상속을 해 준 부모를 가리킨다. 따라서 객체는 __proto__ 프로퍼티가 가리키는 부모객체의 프로퍼티를 사용할 수 있다.
var objA = {
name: "Tom",
sayHello: function () { console.log("hello! "+this.name)};
};
var objB = { name: "Huck"};
objB.__proto__ = objA;
var objC = {};
objC.__proto__ = objB;
objC.sayHello(); // -> hello! Huck
objC의 경우 __proto__ 말고는 어떠한 프로퍼티도 갖고 있지 않지만, sayHello() 메서드를 사용할 수 있다.__proto__ 가 가리키는 __proto__ 를 거슬러 올라가 sayHello 라는 프로퍼티를 찾아내어 실행하기 때문에 가능하다. 즉, objC 의 프로퍼티에 없으므로 __proto__ 가 가리키는 objB 의 프로퍼티를 확인하고, 또 다시 없으므로 objB 의 __proto__ 가 가리키는 objA 의 프로퍼티를 확인하여 sayHello 프로퍼티를 찾고, 사용하게 된다.name 프로퍼티의 경우 갖고있다면 찾아가지 않는다. (프로퍼티를 소유하고 있는 그곳에서 거슬러 올라가는 것을 멈춘다.) 따라서, objC 는 __proto__ 가 가리키는 objB 까지 올라갔더니 name 프로퍼티가 있어서, 이를 사용하고, objA 까지 name을 찾으러 거슬러 올라가지 않는다.__proto__ 프로퍼티가 가리키는 객체를 차례로 거슬러 올라가 검색하는 과정을 프로토타입 체인 (prototype chain) 이라고 한다.__proto__ 프로퍼티가 가리키는 객체가 상속을 해준 부모객체이고, 이 객체를 기존 객체의 프로토타입 이라고 한다.prototype 프로퍼티에 추가하는 방법Object.create() 메서드로 상속받을 프로토타입을 지정하여 객체를 생성하는 방법해당 인스턴스 이름의 빈 객체를 생성
var newInstance = new Object();
// 위를 실행 했을 때 다음과 같은 일이 일어난다.
var newObj = {};
생성자.prototype 을 생성된 객체의 프로토타입(__proto__)으로 설정한다. 이때, 생성자.prototype 이 가리키는 값이 객체가 아니라면 Object.prototype을 프로토타입(__proto__)으로 설정한다.
newObj.__proto__ = Object.prototype;
생성자를 실행하고, 인스턴스를 초기화한다. 이때, this 는 1번에 생성한 객체로 설정하며, 인수는 new 연산자와 함께 사용한 인수를 사용한다.
Object.apply(newObj, arguments);
완성된 객체를 결과값으로 반환한다.
return newObj;
prototype 프로퍼티 값을 인스턴스의 __proto__ 값으로 대입하는 부분에서, 인스턴스 체인이 정의되고, 생성자로 생성한 모든 인스턴스가 생성자의 프로토타입 객체의 프로퍼티를 사용할 수 있게 된다.new 연산자로 생성자를 호출하면 객체의 생성, 프로토타입의 설정, 객체의 초기화를 수행한다.prototype 프로퍼티를 갖게 된다. 이 프로퍼티는 프로토타입 객체(기본값은 빈객체)를 가리키고, 이 프로토타입 객체는 기본적으로 constructor 프로퍼티와 내부프로퍼티 __proto__ 를 갖고 있다.constructor 프로퍼티는 함수객체의 참조를 값으로 갖고 있다.
function F() {};
console.log(F.prototype.constructor); // -> Function F() {};
생성자로 생성한 인스턴스의 경우, 프로토타입 객체의 참조만 갖고 있고, 생성자와는 직접적인 연관고리는 없다.

[[Prototype]](proto)Object.prototype을 가리킨다. (즉, 프로토타입 객체의 프로토타입은 Object.prototype)이다.또한, Object.prototype 의 프로토타입은 null을 가리킨다.
prototype 프로퍼티의 값을 프로퍼티만 정의되어 있는 객체로 교체한다면, constructor 프로퍼티가 없기 때문에, 생성자와의 연결고리가 끊기게 된다.따라서, prototype 프로퍼티를 교체할 경우 constructor 프로퍼티를 가진 객체로 교체하여야한다.
function Circle(center, radius) {
this.center = center;
this.radius = radius;
}
Circle.prototye = {
constructor: Cricle,
area: function() { return ... }
}
인스턴스를 생성한 이후 프로토타입을 교체하면, 연결고리가 끊겨(인스턴스의 프로퍼티는 인스턴스가 생성되는 시점의 프로토타입에서 상속받기 때문.) 이미 생성된 인스턴스는 프로토타입의 메서드나 프로퍼티를 사용할 수 없게된다. 하지만 인스턴스를 생성한 이후 프로토타입의 프로퍼티를 추가하는 것은 가능하다.
function Circle(center, radius) {
this.center = center;
this.radius = radius;
}
c = new Circle({...}, radius);
Circle.prototye = {
constructor: Cricle,
area: function() { return ... }
}
c.area(); // -> Uncaught TypeError
// 프로토타입의 프로퍼티로 메서드를 추가하는 경우에는 가능하다
c1 = new Circle( {...}, radius);
Circle.prototye.area = function() { return ...};
c1.area(); // 제대로 동작
접근자 프로퍼티 하나에 대해, 그 프로퍼티를 읽을 때의 처리를 담당하는 getter 와 setter 를 정의 할 수 있다.
var person = {
// 데이터 프로퍼티 _name 을 정의
_name: "Tom",
// _name 프로퍼티의 값을 조정하는 접근자 프로퍼티 name 의 getter, setter 정의
get name() {
return this._name;
},
set name(value) {
var str = value.charAt(0).toUpperCase() + value.substring(1);
this._name = str;
}
};
console.log(person.name); // Tom
person.name = "huck"; // 접근자 프로퍼티에 값을 대입
console.log(person.name); // huck
name 이라는 이름의 접근자 프로퍼티를 정의하였다. 접근자 프로퍼티 name 은 데이터 프로퍼티 _name 의 값을 읽고 쓰는 일을 담당한다.Object.defineProperty() 메서드를 사용한다.즉시실행함수로 클로저를 생성하면, 데이터 프로퍼티를 객체 외부에서 읽고 쓸 수 없도록 숨기고, 접근자 프로퍼티로만 읽고 쓸 수 있도록 만들 수 있다.
var person = (function () {
var _name = "Tom"; // 클로저를 생성하여 프라이빗 변수로 만듬.
return {
get name() {
return _name;
},
set name(value) {
var str = value.charAt(0).toUpperCase() + value.substring(1);
_name = str;
}
}
})();
console.log(person.name); // -> Tom
person.name = "huck";
console.log(person.name); // -> huck
이런 프로퍼티의 속성은 property descriptor 로 설정할 수 있다. 이는 프로퍼티의 속성을 뜻하는 객체이다.
{
value: 프로퍼티 값,
writable: true/false,
enumerable: true/false
configurable: true/false
}
Object.defineProperty() 메서드를 활용한다.
~~~js var obj = {}; Object.defineProperty(obj, “name”, { value: “Tom”, writable: true, enumerable: false, configurable: true, })
false로 설정된다.객체가 소유한 프로퍼티와 상속받은 프로퍼티 모두가 true 이다.
var person = {name: "Tom"};
console.log("name" in person); // true
console.log("toString" in person); // true
hasOwnProperty() 메서드를 사용하여 프로퍼티가 있는지 확인 할 수 있다.객체가 소유한 프로퍼티인 경우 true, 상속받은 프로퍼티인 경우 false 를 반환한다.
var person = {name: "Tom"};
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false
propertyIsEnumerable() 메서드를 사용하면 그 객체가 소유한 (상속 x) 프로퍼티이며, 열거가 가능한 프로퍼티인지를 확인하여 ture로 반환한다
var person = {name: "tom", age: 17};
var person1 = Object.create(person); // person을 상속받는 person1 객체 생성
person1.name = "huck";
console.log(person1.propertyIsEnumerable("name")); // true
console.log(person1.propertyIsEnumerable("age")); // false 상속받은 프로퍼티
console.log(Object.prototype.propertyIsEnumerable("toString")) // false 열거할 수 없는 프로퍼티
Object.keys() 메서드를 통해 객체가 소유하면서, 열거 가능한 모든 프로퍼티의 이름을 배열로 만들어 반환하게 할 수 있다.객체를 잠구어 수정할 수 없도록 만드는 방법.
Object.preventExtension() 메서드를 사용하여 객체의 확장을 막는 방법이 있다.Object.seal() 메서드로 프로퍼티의 추가를 금지하고, 모든 프로퍼티를 재정의 할 수 없도록 밀봉할 수 있다. 프로퍼티의 추가, 삭제, 수정 을 할 수 없고, 값의 읽기와 쓰기만 가능해진다.Object.freeze() 메서드를 통해 프로퍼티의 추가를 금지하고, 모든 프로퍼티의 재정의를 할 수 없도록 만들며, 값의 읽기만 가능해진다. 단, 접근자 프로퍼티가 정의되어 있으면 게터와 세터 함수를 모두 호출할 수 있다.객체를 Mixin(믹스인) 하는 방법. 객체의 코드 상속과는 다른 방식으로 재사용하는 방법
상속을 사용하지 않는 대신 특정 객체의 프로퍼티를 동적으로 다른 객체에 추가한다.
function mixin(target, source) {
for(var property in source) {
if(source.hasOwnProperty(property)) {
target[property] = source[property];
}
}
return target;
}
mixin 은 원본 객체가 소유하면서 열거할 수 있는 프로퍼티 모두를 타깃객체에 복사하여 다시 반환한다.Object.assign() 메서드를 사용하면 mixin 작업을 쉽게 할 수 있다.다른 프로그램 언어와 데이터 송수신을 간단하게 만드는 데이터 포맷
JSON 포맷을 자바스크립트의 객체 리터럴 표현법에 기반을 두고 있다. 즉, JSON 표기법은 자바스크립트 리터럴 표현법의 부분 집합이다.
{name: "Tom", age: 17, marriage: false, data: [2, 5, null]};
이런 자바스크립트 객체 리터럴은 다음의 JSON과 같다.
'{"name":"Tom", "age":17, "marriage": false, "data": [2, 5, null]}'
이처럼 JSON 데이터는 그 전체를 작은 따옴표로 묶은 문자열이다. 객체의 프로퍼티의 이름은 큰 따옴표로 묶은 문자열로 표기한다.
JSON.stringify()
JSON.stringify() 는 인수로 받은 객체를 JSON 문자열로 변환하여 반환해준다.
JSON.stringify(value[, replacer[, space]])
세번째 인수는 들여쓰기의 단위를 결정한다.
JSON.stringify({x:1, y:2, z:3}, [x, z]);
// -> {"x": 1, "z": 3}
JSON.stringify({x:1, y:2}, null, '\t');
// -> {
// "x": 1,
// "y": 2
// }
JSON.stringify 메서드를 사용할 때 주의할 점. NaN, Infinity, -Infinity 는 null로 변환된다.| Date 객체는 ISO 포맷(2016-10-27T17:13:40+00:00 | 2016-10-27T17:13:40Z)의 날짜 문자열로 직렬화 된다. (JSON.parse로 자바스크립트 객체로 변환할 시, ISO 포맷 그대로의 문자열로 반환됨.) |
Function, RegExp, Error 의 객체, undefined, Symbol 타입은 변환될 수 없다.JSON.parse()
JSON.parse() 메서드는 인수로 받은 문자열을 자바스크립트 객체로 반환한다.
JSON.parse(text[, reviver])
두번째 인수는 프로퍼티의 키와 값을 인수로 받는 함수를 지정할 수 있다. (리턴 값은 환원될 객체의 프로퍼티 값)
JSON.parse('{"x": 0, "y": 1}'); // {x: 0, y: 1}
ES6 는 대격변이라고 할수 있다.
Array, Date 등 기본 생성자의 prototype 에 메서드를 추가해서 확장하는 방법은 권장되지 않는다. 이러한 상황에서도 심볼을 사용한 프로퍼티 이름을 설정하면 타 라이브러리의 프로퍼티 이름과 겹칠 가능성이 없어 안전한 코드가 될 수 있다.심볼을 메서드의 이름으로 사용하여 Array.prototypeㅇ 에 메서드를 정의하는 예제 (Fisher-yates 셔플 알고리즘)
Array.prototype[Symbol.for("shuffle")] = function() {
var a = this;
var m = a.length, t, i.
while (m) {
i = Math.floor(Math.random() * m--); // m미만의 인덱스 i 를 무작위로 골라 하나씩 삭제
t = a[m]; a[m] = a[i]; a[i] = t; // a[i]와 a[m]을 교환
}
return this;
};
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(array[Symbol.for("shuffle")]());
// [3, 5, 8, 4, 1, 9, 2, 6, 7, 0]
프로퍼티 정의의 약식 표기
var prop = 2;
var obj = {prop};
console.log(obj); // -> Object {prop:2}
함수 선언문을 사용
function Card(suit, rank) {
this.suit = suit;
this.rank = rank;
}
Card.prototype.show = function() {
console.log(this.suit + this.rank);
};
함수 리터럴로 정의
var Card = function(suit, rank) {
this.suit = suit;
this.rank = rank;
};
Card.prototype.show = function() {
console.log(this.suit + this.rank);
};
클래스 선언문으로 정의
class Card {
constructor(suit, rank) {
this.suit = suit;
this.rank = rank;
}
show() {
// 이곳에 선언하는 것은
// prototype 객체에 메서드를 선언하는 것과 같다.
console.log(this.suit + this.rank);
}
}
클래스 표현식으로 정의
var Card = class {
constructor(suit, rank) {
this.suit = suit;
this.rank = rank;
}
show() {
console.log(this.suit + this.rank);
}
}
상속을 해주는 부모를 super 생성자, 상속을 받는 자식을 sub 생성자로 일컫을 수 있다.
function Ellipse(a, b) {
this.a = a;
this.b = b;
}
// 타원 넓이를 계산하는 메서드
Ellipse.prototype.getArea = function () {
return Math.PI * this.a * this.b;
};
// toString 메서드를 덮어쓴다
Ellipse.prototype.toString = function () {
return "Ellipse " + this.a + " " + this.b;
}
var ellipse = new Ellipse(5,3);
이렇게 생성자로 인스턴스를 생성시 프로토타입 체인은 다음과 같다. 
Circle.prototype 을 Ellipse.prototype 을 프로토타입으로 가지는 객체로 바꾼다.
console.log(ellipse.getArea()); // 47.123889
console.log(ellipse.toString()); // Ellipse 5 3
생성자의 프로토타입 상속하기
~~~js // 생성자 정의 function Circle(r) { this.a = r; this.b = r; } // 인스턴스 생성 var circle = new Circle(2);
// Circle.prototype 을 Ellipse.prototype 으로 바꾸어 상속 Circle.prototype = Object.create(Ellipse.prototype, { constructor: { configurable: true, enumerable: true, value: Circle, writable: true } });
// Object.prototype 으로부터의 메서드 toString을 덮어씀(오버라이딩) Circle.prototype.toString = function () { return “Circle “ + this.a + “ “ + this.b; }
// 새로운 Circle 객체의 인스턴스 생성 circle2 = new Circle(2);
console.log(circle2.toString()); // Circle 2 2
ES6 에서 추가된 클래스 구문, 문법이 추가된 것이지, 내부가 클래스구조로 바뀐것은 아니다.
따라서, 자바스크립트 객체의 메커니즘과 프로토타입 상속에 대한 이해가 무조건적으로 필요하다.
클래스 선언문
class Circle {
// python 의 __init__ 과 같다.
constructor(center, radius) {
this.center = center;
this.radius = radius;
};
// prototype method
area() {
return Math.PI * this.radius * this.radius;
};
}
이를 함수를 통해 생성자로 만드는 과정으로 보여주면 다음과 같다.
function Circle (center, radius) {
this.center = center;
this.radius = radius;
};
Circle.prototype.area = function() {
return Math.PI * this.radius * this.radius;
};
이 생성자를 생성시, Circle.prototype은 constructor 프로퍼티를 갖고, 그것이 생성자 함수객체 자체를 가리킨다. 이것이 위의 class 구문에서도 드러난것이 보인다.
클래스선언문은 함수선언문과 다음과 같은 차이를 가진다.
클래스 표현식
var Circle = class {
constructor(center, radius) {
this.center = center;
this.radius = radius;
}
area() {
return Math.PI * this.radius * this.radius;
}
}
게터와 세터 설정
class Person {
constructor(name) {
this.name = name;
}
// prototype methods
// define getter
get name() {
return this._name;
}
// define setter
set name(value) {
this._name = value;
}
// method
sayname() {
console.log(this.name);
}
}
name 과 setter 를 통해 생성되는 _name 과 constructor가 인자로 받은 name 은 다른것임을 알아야 한다._name 프로퍼티를 생성하며 대입한다.클래스 멤버 앞에 static 키워드를 붙여 정의할 수 있다.
class Person {
constructor(name) {
this.name = name;
Person.personCount++;
}
// prototype methods
// define getter
get name() {
return this._name;
}
// define setter
set name(value) {
this._name = value;
}
// method
sayname() {
console.log(this.name);
}
static count() {
return Person.personCount
}
}
person1 = Person("hcuk");
console.log(Person.count()); // 1
person2 = Person("tom");
console.log(Person.count()); // 2
extends 키워드를 클래스 선언문이나 클래스 표현식에 붙여주면 다른 생성자를 상속받을 수 있다. 상속받은 이후 새로운 메서드를 추가해서 확장할 수 있다. (프로퍼티 속성까지 바꾸는 기능은 지원하지 않는다.)Circle 생성자를 상속받아 move 메서드를 추가하는 Ball 생성자를 정의해보자.
class Circle {
constructor(center, radius) {
this.center = center;
this.radius = radius;
}
area() {
return Math.PI * this.radius * this.radius;
}
toString() {
return `Circle 중심정 (${this.center.x}, ${this.center.y}) 반지름 = ${this.radius}`;
}
}
// Ball 생성자 생성
class Ball extends Circle {
move(dx, dy) {
this.center.x += dx;
this.center.y += dy;
}
}
// Ball 의 인스턴스 생성
var ball = Ball({x:0, y:0}, 2);
console.log(ball.toString()); // Circle 중심점 (0, 0), 반지름 = 2
console.log(ball.area()); // 12.566370
ball.move(1, 2);
console.log(ball.toString()); // Circle 중심점 (1, 2), 반지름 = 2
자식 생성자는 부모 생성자의 메서드를 덮어쓸 수 있다. 이때, super 키워드를 활용하면 부모의 메서드를 호출할 수 있다.
class Ball extends Circle {
move(dx, dy) {
this.center.x += dx;
this.center.y += dy;
}
toString() {
var str = super.toString();
return str.replace("Circle", "Ball");
}
}