기존 블로그에 작성한 내용을 velog로 이전한 글입니다
객체: 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료 구조
프로그램에 필요한 속성만을 간추려 표현하는 '추상화'를 하는것
자바스크립트는 프로토타입을 통해 객체를 상속하여 중복 생성을 막는다.
__proto__
접근자 프로퍼티
모든 객체는 자신의 프로토타입([[Prototype]]
내부 슬롯)에 접근하기 위한
__proto__
접근자 프로퍼티를 가지고 있다.
객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다.
__proto__
접근자 프로퍼티는 상속을 통해 사용된다.
const animal = { type: "dog" };
// animal 객체는 __proto__ 프로퍼티를 소유하지 않는다.
console.log(animal.hasOwnProperty("__proto__")); // false
// __proto__ 프로퍼티는 모든 객체의 프로토타입 객체인 Object.prototype의 접근자 프로퍼티이다.
console.log(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__"));
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}
// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); // true
함수 객체의 prototype 프로퍼티
함수 객체는 prototype 프로퍼티도 소유한다.
위 그림과 같이 프로토타입과 __proto__
는 같은 프로토타입을 가리킨다.
단 프로토타입은 함수 객체가 소유하며 인스턴스의 프로토타입을 할당하기 위해 사용하지만, __proto__
는 모든 객체가 자신의 프로토타입에 접근하기 위해 사용한다.
모든 프로토타입은 constructor 프로퍼티를 갖는다.
이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.
리터럴 표기법에 의해 생성된 객체는 생성자 함수에 의해 생성된 객체는 아니다.
객체 리터럴로 생성한 객체와 Object 생성자 함수로 생성한 객체는 생성 과정에 차이는 있지만 결국 객체로서 동일한 특성을 갖는다.
리터럴 | 표기법 | 생성자 함수 | 프로토타입 |
---|---|---|---|
객체 | 리터럴 | Object | Object.protptype |
함수 | 리터럴 | Function | Function.prototype |
배열 | 리터럴 | Array | Array.prototype |
정규 표현식 | 리터럴 | RegExp | RegExp.protptype |
사용자 정의 생성자 함수와 프로토타입 생성 시점
함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
// 함수 정의(constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(animal.prototype); // {constructor: ƒ}
// 생성자 함수
function animal(type) {
this.type = type;
}
빌트인 생성자 함수와 프로토타입 생성 시점
모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다.
__proto__
를 통해 프로퍼티를 검색하는 것을 프로토타입 체인이라고 한다.
캡슐화는 정보의 일부를 외부에 감추어 은닉하는 것을 말한다.
const Animal = (function () {
let _cry = ""; // private한 변수
// 생성자 함수
function Animal(cry) {
_cry = cry;
}
// 프로토타입 메소드 (상속됨)
Animal.prototype.cry = function () {
console.log(`Animal cry : ${_cry}`);
};
// 생성자 함수를 반환
return Animal;
})();
const dog = new Animal("bow");
dog.cry = "meow"; // _type은 지역변수로 캡슐화 돼있기 때문에 변경되지 않는다.
dog.cry(); // Animal cry : bow
const Animal = (function () {
let _cry = ""; // private한 변수
// 생성자 함수
function Animal(cry) {
_cry = cry;
}
// 프로토타입 메소드 (상속됨)
Animal.prototype.cry = function () {
console.log(`Animal cry : ${_cry}`);
};
// 생성자 함수를 반환
return Animal;
})();
const dog = new Animal("bow");
dog.cry = function () {
console.log(`Dog cry : ${_cry}`);
};
dog.cry(); // Dog cry : bow <- 인스턴스 메소드가 호출됨
인스턴스 메소드 cry가 프로토타입 메소드 cry를 오버라이딩 한다.
생성자 함수에 의한 교체
const Person = (function () {
function Person(name) {
this.name = name;
}
// ① 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Lee");
위 그림처럼 객체 리터럴이 프로토타입으로 들어갔기 때문에 constructor 프로퍼티가 없다.
프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 링크가 파괴된다.
이를 연결하기 위해선 다음과 같이 명시적으로 연결해 줘야 한다.
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 링크 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
},
};
인스턴스에 의한 교체
function Person(name) {
this.name = name;
}
const me = new Person("Lee");
// 프로토타입으로 교체할 객체
const parent = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
},
};
// me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작한다.
// me.__proto__ = parent;
me.sayHello(); // Hi! My name is Lee
constructor 프로퍼티와 생성자 함수 간의 링크가 파괴될 뿐만 아니라
생성자 함수의 prototype 프로퍼티와의 연결도 끊어진다.
이를 다시 살리려면 다음과 같이 작성해야 한다.
// 프로토타입으로 교체할 객체
const parent = {
// constructor 프로퍼티와 생성자 함수 간의 링크 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
},
};
// 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 링크 설정
Person.prototype = parent;
객체 instanceof 생성자함수;
좌변의 객체가 우변의 생성자 함수와 연결된 인스턴스라면 true, 아니면 false로 평가된다.
이 평가는 상속 관걔를 고려한다.
우변의 생성자함수의 프로토타입이 좌변의 객체 프로토타입 체인상에 존재하는지 확인한다.
Object.create에 의한 직접 상속
// Object.create(프로토타입, 프로퍼티로 가질 객체);
obj = Object.create(Object.prototype, {
x: { value: 1 },
}); // obj = Object.create(Object.prototype);와 같다
첫번째 인수로 프로토타입, 두번째 인수로 프로퍼티로 가질 객체를 전달한다.
객체 리터럴 내부에서 __proto__
에 의한 직접 상속
const myProto = { x: 10 };
const obj = {
y: 20,
__proto__: myProto,
}; // // const obj = Object.create(myProto, { y: { value: 20 } });
생성자 함수로 인스턴스를 만들지 않고도 참조하거나 호출할 수 있는 프로퍼티/메소드를 뜻한다.
function Animal(cry) {
this.cry = cry;
}
Animal.staticProp = "static prop";
Person.staticMethod = function () {
console.log("staticMethod");
};
Person.staticMethod();
key in object;
const puppy = {
type: "dog",
age: 5,
};
console.log("type" in puppy); // true
console.log(puppy.hasOwnProperty("age")); // true
in 연산자는 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의해야한다.
따라서 hasOwnProperty
를 사용하면 해당 객체의 프로퍼티만 확인할 수 있다.
for...in문
for...in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]
의 값이 ture인 프로퍼티를 순회하며 열거한다.
따라서 객체 자신의 프로퍼티 만을 열거하려면 Object.prototype.hasOwnProperty
메소드를 사용하여 객체 자신의 프로퍼티인지 확인해야 한다.
Object.keys/values/entries 메소드
Object.keys
메소드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.
Object.values
메소드는 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환한다. (ES8)
Object.entries
메소드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다. (ES8)
const puppy = {
type: "dog",
age: 5,
__proto__: { age: 20 },
};
console.log(Object.keys(puppy)); // ["type", "age"]
console.log(Object.values(puppy)); // ["dog", "5"]
console.log(Object.entries(puppy)); // [["type", "dog"], ["age", 5]]