모던 자바스크립트 입문 9장 객체 부분을 읽고 정리한 내용입니다.
1. 객체의 생성
// 1. 객체 리터럴로 생성
const fruit = { name = 'apple', season = 'fall'};
// 2. 생성자로 생성
function Fruit(name, season) {
this.name = name;
this.season = season;
}
const fruit = new Fruit('apple', 'fall');
// 3. Object.create로 생성
const fruit = Object.create(Object.prototype, {
name: {
value: 'apple',
writable: true,
enumerable: true,
configuragle: true
},
season: {
value: 'fall',
writable: true,
enumerable: true,
configuragle: true
}
});
프로토타입
js는 class 개념 대신 prototype이 존재하는데 이를 통해 상속을 구현함. js 객체는 [[prototype]](__proto__) 속성으로 부모객체를 가리킨다. 자바의 class가 최상위에 Object클래스를 상속 받는 것처럼 js 객체는 __proto__속성으로 Object를 가리킨다.
-
생성자로 메서드 정의하는 방식의 문제점
메서드를 인스턴스 개수만큼 생성하여 메모리를 낭비함
function Fruit(name, season, price) { this.name = name; this.season = season; this.getPrice = function(season) { if (this.season === season) return price; else return price*1.2; }; } const apple = new Fruit('apple','fall',1000); const tangerine = new Fruit('tangerine', 'winter', 200); const strawberry = new Fruit('strawberry ', 'spring', 300); console.log(apple.getPrice('winter')); console.log(tangerine.getPrice('winter')); console.log(strawberry.getPrice('winter'));
-
프로토타입 객체
함수는 prototype 속성을 가진다. (__proto__ 속성과 별개)
prototype 속성은 자신의 prototype 객체로 자식 객체(생성자로 생성한 인스턴스)가 참조하는 객체이다.
따라서 생성자의 prototype 객체에 속한 속성은 해당 prototype 객체를 프로토타입으로 가지는 모든 인스턴스(생성자로 생성한 인스턴스)에서 사용할 수 있다.
(말이 참 헷갈리네..코드로 보시고 __proto__ 와 prototype차이는 아래서 다시 설명합니다)
function Fruit() {}; console.log(Fruit.prototype); Fruit.prototype.prop = 'proto val'; const fruit = new Fruit(); console.log(fruit.prop); // proto val fruit.prop = 'instance val'; console.log(fruit.prop); // instance val console.log(Fruit.prototype.prop); // proto val
-
프로토타입에 메서드 정의하기
인스턴스 안에 메서드가 존재하지 않아도 메서드를 사용할 수 있다.
또한, 메서드 안의 this는 생성한 인스턴스를 가리킨다.
// 프로토타입에 메서드 정의하기 function Fruit(name, season, price) { this.name = name; this.season = season; this.price = price; } Fruit.prototype.getPrice = function(season) { if (this.season === season) return this.price; else return this.price*1.2; }; // this는 생성한 인스턴스를 가리킴 Fruit.prototype.getSeason = function() { console.log(`${this.name}은 ${this.season}에 제철입니다.`); }; const apple = new Fruit('apple','fall',1000); const tangerine = new Fruit('tangerine', 'winter', 200); const strawberry = new Fruit('strawberry ', 'spring', 300); // 인스턴스 내에 메소드가 없어도 프로토타입에 정의했기 때문에 사용가능 console.log(apple.getPrice('winter')); console.log(tangerine.getPrice('winter')); console.log(strawberry.getPrice('winter')); apple.getSeason();
2. 프로토타입 상속
js에서 상속은 프로토타입 체인으로 구현된다. 객체의 __proto__속성은 부모 객체(원형 객체)를 가르킨다. 속성값을 찾을 수 없으면 프로토타입을 거슬러 올라가 속성 값을 찾아 실행한다.
const parent = {
name: 'p name',
sayHello: function() { console.log(`Hello! I'm ${this.name}`); }
};
const child = {
name: 'c name'
};
child.__proto__ = parent;
const grandchild = {
name: 'g name'
};
grandchild.__proto__ = child;
parent.sayHello();
child.sayHello();
grandchild.sayHello();
debugger;
// 프로토타입 가져오기
console.log(Object.getPrototypeOf(child));
new 연산자
function Fruit(name, season, price) {
this.name = name;
this.season = season;
this.price = price;
}
Fruit.prototype.getPrice = function(season) {
if (this.season === season) return this.price;
else return this.price*1.2;
};
// new 연산자를 이용한 인스턴스 생성 과정
const apple = new Fruit('apple','fall',1000);
// 1. 빈 객체 생성
const apple = {};
// 2. 프로토타입 설정
// 이떄, 프로토타입이 객체가 아니면 Object.prototype으로 설정
apple.__proto__ = Fruit.prototype;
// 3. 생성자를 싱행하고 객체를 초기화.
Fruit.apply(apple, arguemnts);
// 4. 객체 반환
return apple;
프로토타입 객체의 프로퍼티
함수를 정의 하면 prototype 속성(__proto__와는 다른 속성) 을 가지는데 prototype 속성은 constructor와 __proto__ 를 포함한다.
function myfn() {};
console.log(myfn.prototype.constructor);
// -> function myfn() {}
console.log(myfn.prototype);
// -> constructor
debugger;
함수와 함수의 프로토타입 객체는 서로를 참조하고 있다. 함수의 prototype 속성이 프로토타입 객체를 가리키고, 이 프로토타입 객체의 constructor 프로퍼티가 함수를 가리킨다.
반면 생성자(함수)로 생성한 인스턴스는 프로토타입의 객체만 참조하고 생성자(함수)와는 연결고리가 없다. 대신 프로토타입을 통해 constructor 속성을 상속받기 때문에 생성자에 접근할 수 있다.
인스턴스는 생성시점의 생성자의 프로토타입을 상속받는다. 즉, 생성 이후 생성자의 프로토타입을 수정하면 인스턴스에는 반영되지 않는다.
생성자의 prototype의 __proto__는 Object의 prototype을 가리킨다.
function Fruit(name, season, price) {
this.name= name;
this.season= season;
this.price= price;
}
const banana = new Fruit('banana', 'spring', 500);
console.log(banana.constructor);
console.log(Fruit.prototype.__proto__); // Object.prototype
Fruit.prototype = {
constructor: Fruit,
getPrice: function() { console.log(`가격 : ${this.price}`);}
}
const apple = new Fruit('apple', 'fall', 1000);
//인스턴스 생성 후에 생성자의 프로토타입을 수정하면 인스턴스에서는 반영되지 않는다.
apple.getPrice();
~~banana.getPrice();~~
__proto__ 와 prototype 속성
__proto__ 는 모든 객체가 가지는 속성이고 부모 객체의 prototype 속성을 가리킨다.
prototype 속성은 함수객체만 가지고 있으며 자신의 prototype 객체로서 자식 객체가 __proto__ 속성으로 이를 참조한다.
프로토타입의 확인
// 프로타타입이 특정 객체의 프로토타입 체인에 포함되어있는지 확인
// instanceof 연산자
객체 instanceof 생성자
// insPrototypeOf 메서드
프로토타입.isPrototypeOf(객체)
function F() {};
const obj = new F();
console.log(obj instanceof F);
console.log(F instanceof Object);
console.log(F.prototype.isPrototypeOf(obj));
console.log(Object.prototype.isPrototypeOf(obj));
const apple = {};
const banana = Object.create(apple);
console.log(apple.isPrototypeOf(banana));
Object.prototype
Obejct.prototype은 프로토타입 체이닝으로 모든 객체에 포함된다. 따라서 모든 객체에서 Obejct.prototype의 속성을 사용할 수 있다.
// Object 생성자
const obj = new Object();
= Object();
= {};
// Object 생성자의 메서드
// Object.assign(), obj.constuctor.assign() 이런식으로 사용
assign(obj1, obj2, ..., objn) //열거 한 객체들의 속성을 복사하여 객체 반환
create(proto) // proto를 prototype으로 가지는 객체 생성
getOwnPropertyNames(obj) //obj 객체가 가진 모든 프로퍼티 명을 배열로 반환
keys(obj) // obj객체가 가진 열거 가능한 프로퍼티 명을 배열로 반환
getPrototypeOf(obj) // 인수로 지정한 객체의 프로토타입을 반환
// Object.prototype의 메서드 : 모든 객체에서 사용가능
hasOwnProperty(key) // key 값에 해당하는 프로퍼티의 존재 유무 확인
isPrototypeOf(obj) // obj로 지정한 객체의 프로토타입인지 확인
valueOf()
Object.create로 객체 생성
Object.create( {프로토타입}, {생성할 객체의 프로퍼티});
const apple = {
name: 'apple',
season: 'fall',
sayHello: function() {
console.log(`${this.name}은 ${this.season}에 제철입니다.`);
}
}
const banana = Object.create(apple);
banana.name = 'banana';
banana.sayHello();
//빈 객체{} 생성
const obj = Object.create(Object.prototype);
3. 접근자 Getter, Setter
객체는 값을 저장하기 위한 데이터 속성과 속성을 읽고 쓸 때 호출하는 함수를 지정하는 접근자 속성(getter, setter)이 있다.
특정 속성에 대해 getter와 setter를 정의 하면 해당 속성을 사용할 때 getter와 setter를 호출해서 사용한다.
const fruit = {
_name: 'apple',
get name() {
console.log('getter 호출');
return this._name; },
set name(name) {
console.log('setter 호출');
this._name = name; }
}
console.log(fruit.name); // getter 호출
fruit.name = 'banana'; // setter 호출
console.log(fruit.name); // getter 호출
// 접근자가 있어도 여전히 데이터를 직접 읽고 쓸 수 있다
console.log(fruit._name);
//클로저를 생성해 데이터 캡슐화
// -> 데이터 직접 접근 불가
const fruit = (function() {
let _name = 'ff';
return {
get name() {
console.log('getter 호출');
return _name;
},
set name(name) {
console.log('setter 호출');
_name = name;
}
};
})();
console.log(fruit.name); // getter 호출
fruit.name = 'banana'; // setter 호출
console.log(fruit.name); // getter 호출
console.log(fruit._name); // undefined
4. 프로퍼티의 속성
/*
프로퍼티의 속성
writable (쓰기 여부)
enumerable (열거 여부)
configurable (프로퍼티의 내부 속성의 재정의 여부)
->false이면 configurable 속성도 변경 불가. writable만 한번 true->false 변경 가능
*/
// **내장 생성자의 프로토타입 객체의 프로퍼티는 대부분 쓰기 가능/열거 불가능/재정의 가능
// 1. PropertyDescriptor : 프로퍼티 속성 기술자
//데이터 속성
{
value: 속성 값,
writable: true/false,
enumerable: true/false,
configurable: true/false
}
//접근자 속성
{
get: getter 함수값,
set: setter 함수값,
enumerable: true/false,
configurable: true/false
}
// 2. PropertyDescriptor 가져오기
//Object.getOwnPropertyDescriptor
const apple = { name: 'apple' };
Object.getOwnPropertyDescriptor(apple,'name');
// {value: "apple", writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors(apple);
// 3. 객체의 프로퍼티 설정(변경)하기
// Object.defineProperty
const banana = {};
Object.defineProperty(banana, 'name', {
value: 'banana',
writable: true,
enumerable: false,
configurable: true
});
// 4. 객체의 프로퍼티 복수개 설정하기
// Object.defineProperties
const cherry = Object.defineProperties({},{
_name: { value:'cherry', writable:true, enumerable:true, configurable:true },
name: { get: function() {return this._name;},
set: function(value) { this._name = value;},
enumerable:true, configurable:true }
});
Object.create()
//Object.create({prototype}, {PropertyDescriptor});
const apple = Object.create(Object.prototype, {
name: {
value: 'apple',
writable: true,
enumerable: true,
configuragle: true
},
season: {
value: 'fall',
writable: true,
enumerable: true,
configuragle: true
}
});
5. 프로퍼티 확인
const apple = { name: 'apple'};
// 1. in 연산자
console.log('name' in apple); //true
console.log('price' in apple); //false
console.log('valueOf' in apple); // true 상속 받은 속성
// 2. hasOwnProperty 메서드
console.log(apple.hasOwnProperty('name'));
console.log(apple.hasOwnProperty('price'));
// 3. propertyIsEnumerable 메서드
// 객체가 소유한 속성이며 열거 가능한지 확인
const fruit = { price: 100 };
const banana = Object.create(fruit);
banana.name = 'banana';
console.log(banana.propertyIsEnumerable('name')); //true
console.log(banana.propertyIsEnumerable('price')); //false
6. 프로퍼티 열거
const parent = { pName: 'p name'};
const child = Object.create(parent);
child.name = 'c name';
child.attr = 'c attr';
child.sayHello = function() { console.log(`hello! I'm ${this.name}`);};
Object.defineProperty(child, 'sayHello', {enumerable:false});
// 속성 열거
// 1. for-in문
// 프로토타입의 속성도 열거
for(let p in child) console.log(p);
// 2. Object.keys 메서드
// 해당 객체가 소유한 프로퍼티명 조회
const p = Object.keys(child);
for(let i=0 ;i<p.length; i++){
console.log(`${p[i]} : ${child[p[i]]}`);
}
// 3. Object.getOwnPropertNames 메서드
// 해당 객체가 소유한 프로퍼티명 조회
// 열거할수 없는 프로퍼티 명도 조회
const p = Object.getOwnPropertyNames(child);
for(let i=0 ;i<p.length; i++){
console.log(`${p[i]} : ${child[p[i]]}`);
}
7. 객체 잠그기
객체를 수정할 수 없게 만듦
// 1. 확장 방지 extensible : 객체의 새로운 속성을 추가 할 수 있는지 여부
//Object.preventExtensions(obj) extensible -> false
const fruit = { name: 'apple'};
Object.preventExtensions(fruit);
fruit.price = 1000;
console.log('price' in fruit); // false
console.log(Object.isExtensible(fruit)); // false
// 2. 밀봉
// seal : 속성을 추가할 수도 재정의 할 수도 없음. 속성 값 읽고 쓰기만 가능
const fruit = { name: 'apple'};
Object.seal(fruit);
// 아래 소스는 적용안됨
fruit.price = 1000;
delete fruit.name;
Object.defineProperty(fruit,'name',{enumerable:false});
//읽기 쓰기만 가능
fruit.name = 'banana';
console.log(fruit.name);
// 3. 동결
// freeze : 속성 추가, 재정의, 쓰기 불가. 읽기만 가능
const fruit = { name: 'apple'};
Object.freeze(fruit);
fruit.name = 'banana';
console.log(fruit.name); // apple
8. Mixin
서로다른 객체의 속성들을 뒤섞는 것 ↔ 상속
function mixin(target, source) {
const keys = Object.keys(source);
for(let i=0; i<keys.length; i++) {
let descriptor = Object.getOwnPropertyDescriptor(source, keys[i]);
Object.defineProperty(target, keys[i], descriptor);
}
return target;
}
const fruit = {
_name: 'apple',
get name() {
return this._name;
}
};
const fruit2= {};
mixin(fruit2, fruit);
fruit2.name = 'banana';
console.log(fruit2.name);
console.log(fruit2);
9. JSON
네트워크를 통해 데이터를 주고받을 떄 사용하는 데이터 포맷
//객체 리터럴
{name : 'apple', price: 1000, months: [9,10,11] };
//JSON 데이터
'{"name":"apple", "price":1000, "month":[9,10,11]}'
// JSON.stringify : js Object -> JSON
const fruit = {name : 'apple', price: 1000, months: [9,10,11] };
data = JSON.stringfity(fruit);
// JSON.parse : JSON -> js Object
JSON.parse(data); // -> fruit과 동일
// 주의사항
// 1. NaN, Infinity 는 null로 직렬화
// 2. Date 객체는 날짜 문자열로 직렬화
// 3. 함수, 에러, undefined, 심볼은 직렬화 불가
// 4. 열거 가능한 속성만 직렬화
__proto__와 prototype의 차이를 알자.
__proto__는 모든 객체가 가지는 속성으로 원형 객체를 가리킨다.
prototype은 함수객체만 가지는 속성으로 자신의 prototype 객체를 가리킨다.
함수객체의 prototype도 결국 Object.prototype으로 만들어졌다.
모든 객체의 뿌리에는 Obejct생성자가 존재
'web > javascript' 카테고리의 다른 글
[javascript]모던 자바스크립트 - 객체(가비지 컬렉션, this, new, 생성자, 심볼, 형 변환) (0) | 2021.01.27 |
---|---|
[javascript]모던 자바스크립트 - 기본 (변수와 상수, 자료형, 함수, 함수표현식, 화살표 함수) (0) | 2021.01.25 |
댓글