: key와 value의 쌍으로 이루어져 있다.
JavaScript의 객체는 내부 slot과 내부 method를 가진다.
내부 slot -> [[...]]
내부 method -> [[...]]
-> 이것도 property이다. 개발자가 직접적으로 사용할 수 없다. JavaScript engine에 의해 사용한다.var obj = { name: '홍길동' }; console.dir(obj);
obj . __Proto__ -> [[prototype]]
: 문자열, symbol
: JavaScript에서 value로 인식되는 모든 것
ex) 문자열, symbol, 숫자 ...
함수도 value가 될 수 있다. -> method
: property를 생성할 때 해당 property의 상세를 나타내는 값. 기본적으로 정의된다. 내부 슬롯이라서 직접적으로 접근할 수 있는 방법이 없다. 대신 간접적인 사용은 가능하다.
property의 값 [[Value]]
property 값을 수정할 수 있는지 여부 [[Writable]]
: 고정한다는 것은 key에 대한 value를 고정한다는 것이다.
해당 property가 열거될 수 있는지 여부 [[Enumerable]]
: 열거될 수 있다면 for문을 돌릴 수 있다.
property attribute를 재정의 할 수 있는지 여부 [[Configurable]]
: Object.getOwnPropertyDescriptor(), Object.getOwnPropertyDescriptors()
const person = {
name: 'Lee',
age: 20
};
// property 한 개 가지고 올 때
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// property 모두를 가지고 올 때
console.log(Object.getOwnPropertyDescriptors(person));
실행 결과 : { value: 'Lee', writable: true, enumerable: true, configurable: true }
{
name: {
value: 'Lee',
writable: true,
enumerable: true,
configurable: true
},
age: { value: 20, writable: true, enumerable: true, configurable: true }
}
: Object.defineProperty()
const person = {};
Object.defineProperty(person, 'name', {
value: '홍길동',
writable: false,
enumerable: false,
configurable: true
});
console.log(person);
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
person.name = '아이유'; // writable이 false이기 때문에 바뀌지 않는다.
console.log(Object.keys(person)); // enumerable이 false이기 때문에 key들이 열거되지 않는다.
실행 결과 : {} // 브라우저에서 확인 가능
{
value: '홍길동',
writable: false,
enumerable: false,
configurable: true
}
: 일반 객체에 비해서 더 가지고 있는 property가 존재한다.
function squre(number) {
return number * number;
}
console.dir(squre);
arguments property
: arguments 유사배열 객체를 property의 value로 가지고 있다.
함수 내에서 지역 변수처럼 사용한다.
caller property
: 함수 자신을 호출한 함수에 대한 reference를 가지고 있다.
비표준. ECMA에는 표준으로 지정되어 있지 않다. 가능한 사용하지 않는 것이 좋다.
function foo(f) { // 고차 함수
return f(); // 콜백 함수
}
function bar() {
return 'caller: ' + bar.caller; // 자기 함수 안이기 때문에 함수 이름을 사용할 수 있다.
}
console.log(bar());
실행 결과 : caller: null
caller: function foo(f) { // 고차 함수
return f(); // 콜백 함수
}
// 브라우저에서 확인 가능하다.
length property
: 매개변수(parameter)의 개수이다.
name property
: 함수의 이름이다.
prototype property
: 해당 함수(생성자 함수)가 생성하는 instance가 내부 슬롯 [[Prototype]]으로 참조하는 객체를 가리킨다.
constructor(함수 선언문, 함수 표현식, class)만 가질 수 있는 property이다.
따라서 일반 객체(객체 literal), non-constructor(arrow function, ES6의 method)는 prototype property를 가지지 않는다.
constructor 함수 객체에 여러 property가 존재한다.(arguments, caller, ...) 가장 마지막에 prototype property가 있다.
생성자 함수이기 때문에 new를 사용하면 생성자 함수의 instance가 만들어진다.
모든 객체는 [[Prototype]] 내부 슬롯을 가진다.
[[Prototype]] 내부 슬롯은 자신의 상위 prototype 객체를 가리킨다.
상위 prototype 객체는 생성자 함수 객체의 prototype property가 가리키는 객체이다.
[[Prototype]] 내부 슬롯은 script engine이 사용하는 것으로 개발자가 사용할 수 없다. 따라서 사용할 수 있게 접근자 property(__proto__)가 존재한다. 이 property도 상위 prototype 객체를 가리키고 있다.
접근자 property(__proto__)는 상속이 되었기 때문에 사용이 가능한 것이다.
-> Object에서 상속. Object라는 생성자 함수에도 prototype 객체가 있다. (Object.prototype.__proto__)
생성자 함수의 prototype 객체의 [[Prototype]] 내부 슬롯이 Object의 prototype 객체를 가리키고 있다. Object의 prototype 객체의 constructor는 Object 생성자 함수를 가리킨다.
생성자 함수의 [[Prototype]] 내부 슬롯은 Function.prototype 객체를 가리킨다. Function.prototype 객체의 constructor는 Function이라는 생성자 함수를 가리킨다.
Object의 prototype 객체의 [[Prototype]]에는 null이 저장되어 있다.(prototype chain의 최상위)
prototype chain
: 내가 사용하려는 property를 chain을 따라서 찾아 가는 것
prototype chain이 생성자 함수 기준, 인스턴스 기준으로 2개가 존재하게 된다. Person -> Function -> Object
instace -> Person -> Object
: [[Prototype]] 내부 슬롯의 참조를 따라갈 수 있다. Object prototype이 가지고 있는 property.
그런데, "__proto__" 표현이 code에 직접 나오는 것은 권장하지 않는다.
const obj = {};
const parent = { x: 1 };
obj.__proto__ = parent; // parent를 상속받을 수 있게 된다.
console.log(obj.x);
실행 결과 : 1
const obj = {}; // 객체 literal로 만든 객체
console.log(obj.__proto__.constructor.name);
실행 결과 : Object
소유 | 의미 | |
---|---|---|
__proto__ property | 모든 객체 | prototype 객체의 참조 |
prototype property | constructor | prototype 객체의 참조 |
var obj = { name: '홍길동' };
객체가 생성되고 key와 value name과 '홍길동'이 들어간다.
객체의 [[Prototype]] 을 따라가면 Object.prototype 객체가 있다.
Object.prototype의 constructor에는 Object 생성자 함수가 있다.
var obj = new Object()
obj.name = '홍길동';
: 두 방식으로 만들어진 객체는 결론적으로는 똑같이 만들어지지만 메카니즘이 다르기 때문에 똑같다고 할 수 없다.
// 함수 표현식
var foo = function() {};
console.log(foo.__proto__ === Function.prototype);
// foo에는 prototype이 없으므로 function의 prototype으로 간다.
console.log(foo.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__);
console.log(foo.constructor === Function);
실행 결과 : true
true
null
true
상위 prototype / 생성자 함수
- {} 객체 리터럴 - Object prototype / Object
- 함수 리터럴 - Function prototype / Function
- [1, 2, 3] 배열 리터럴 - Array prototype / Array
: 생성자 함수와 같이 만들어진다.
: 상속을 구현하기 위해서(Inheritance), 코드의 재활용을 높이기 위해서
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
}
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
// getDiameter 함수가 계속해서 만들어진다.
// 공통으로 하나 만들어지는 것이 좋다.
console.log(circle1.getDiameter === circle2.getDiameter);
실행 결과 : false
: 함수를 공통으로 사용하기 위해 Circle의 prototype 객체에 함수를 넣어 놓는다. instance의 객체에 함수가 존재하지 않으면 prototype chain을 통해 Circle의 prototype 객체를 찾아가기 때문에 공통으로 함수를 사용할 수 있다.
// 생성자 함수
function Circle(radius) {
this.radius = radius;
Circle.prototype.getDiameter = function() {
return 2 * this.radius;
}
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getDiameter === circle2.getDiameter);
실행 결과 : true
: Circle 객체의 prototype property를 찾아가면 prototype 객체를 찾을 수 있다. 이 객체에 함수를 property로 설정한다.
// 생성자 함수
function Circle(radius) {
this.radius = radius;
Circle.prototype.getDiameter = function() {
return 2 * this.radius;
}
// 공용변수
Circle.prototype.name = '홍길동';
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.name, circle2.name); // 홍길동 홍길동
// circle1에 새롭게 property를 추가하기 때문에 바뀌지 않는다.
//circle1.name = '아이유';
//console.log(circle1.name, circle2.name); // 아이유 홍길동
Circle.prototype.name = '아이유'; // 이렇게 해야 바뀐다.
console.log(circle1.name, circle2.name);
circle1.__proto__.name = '김연아'; // 이런 방법으로도 바꿀 수 있다.
console.log(circle1.name, circle2.name);
실행 결과 : 홍길동 홍길동
아이유 아이유
김연아 김연아
: 상위로 prototype chain을 따라 올라가서 공유 변수의 값을 바꿀 수가 없다. 직접 접근해야 바꿀 수 있다.
: 만약 overriding이 발생하면 발생된 overriding에 의해서 숨겨진 prototype 메소드를 property shadowing 되었다고 한다.
function Person(name) {
this.name = name;
// instance 메소드
// this.getName = function() {}
}
// prototype 함수 선언
Person.prototype.sayHello = function() {
console.log(`안녕하세요. ${this.name}`);
}
// instance를 생성
const me = new Person('홍길동');
// overriding
me.sayHello = function() {
console.log(`Hello ${this.name}`);
}
me.sayHello();
실행 결과 : Hello 홍길동
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello() {
console.log('안녕하세요!');
}
}
const me = new Person('홍길동');
console.log(me.constructor === Object);
실행 결과 : true
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
sayHello() {
console.log('안녕하세요!');
}
}
const me = new Person('홍길동');
console.log(me.constructor === Person);
실행 결과 : true
function Person(name) {
this.name = name;
}
const me = new Person('홍길동');
const parent = {
sayHello() {
console.log('안녕하세요!');
}
}
// me의 prototype 객체를 parent로 바꾼다.
Object.setPrototypeOf(me, parent);
console.log(me.__proto__ === Person.prototype);
실행 결과 : false
: property chain에 속하지 못하기 때문에 instance로 사용하지 못한다. 함수로 직접 접근해서 사용해야 한다.
function Person(name) {
this.name = name;
// instance 메소드
this.callme = function() {}
}
// prototype 메소드
Person.prototype.sayHello = function() {
console.log('안녕안녕!!');
}
// static 메소드
Person.staticMethod = function() {
console.log('하이하이!');
}
정리
JavaScript engie이 시작 -> built-in 객체 생성 -> 전역 객체 생성(환경에 따라 만들어지는 객체가 다르다.) 브라우저에서는 window 객체가 생성된다. -> 코드를 읽으면서 함수 선언문을 만나면 함수 이름으로 된 property가 window에 생긴다. -> 메모리 어딘가에 있는 함수 객체를 property가 가리키고 있다. -> 모든게 window.으로 시작하기 때문에 생략하고 쓰지 않는 것이다.
변수도 마찬가지이다. const와 var로 변수를 선언하면 window의 property로 들어간다. let은 들어가지 않는다.
기억해야 할 property
prototype : 생성자 함수 객체
constructor : 생성자 함수의 prototype 객체
__proto__ : 인스턴스