prototype 프로퍼티는 constructor 함수만 가지고 있음
constructor 프로퍼티는 constructor가 가리키는 prototype 객체만 가지고 있음
자바스크립트 엔진은 가장 먼저 global execution context(코드를 실행하기 위해 전역 객체, built-in 생성자 함수, 전역객체 등을 저장하기 위한 메모리 공간)을 할당하여 코드 실행에 필요한 모든 객체 등을 저장한다.
전역 실행 컨텍스트를 참조하여 코드를 실행
함수를 실행하면 함수 실행 컨텍스트가 따로 만들어져 전역 실행 컨텍스트와 link 되고, 함수 스코프 지정과 파라미터, 인수, 지역변수 등 함수와 관련된 정보들을 함수 실행 컨텍스트에 저장된다.
: Object.prototype이 가지고 있는 property
모든 객체는 [[Prototype]] 내부 슬롯을 가지고 있지만 내부 슬롯에 접근 불가
대신 "__proto__" 라는 property로 접근 가능하게 만들어졌다.
```
const obj = {};
const parent = { x: 1 };
obj.__proto__ = parent; // obj는 parent를 상속받을 수 있음
console.log(obj.x); // 1
```
만약 어떤 객체 또는 인스턴스의 상위 객체를 알고 싶다면 "__proto__"를 이용
const obj = {};
console.log(obj.__proto__.constructor.name); // Object
그러나 "__proto__" 표현이 코드에 직접 나오는 것은 권장되지 않음
const obj = Object.create(null);
console.log(obj.__proto__); // undefined
따라서 이러한 경우 Object의 메서드를 사용하는 것 권장console.log(Object.getPrototypeOf(obj)); // null
소유 | 의미 | |
---|---|---|
__proto__ | 모든 객체(Object 상속) | prototype 객체의 참조 |
prototype | constructor | prototype 객체의 참조 |
Person 생성자 함수 | Person.prototype | |||
---|---|---|---|---|
prototype | Person.prototype | constructor | Person 생성자 함수 | |
__proto__ | Function.prototype | __proto__ | Object.prototype |
var obj = { name: '홍길동' };
var obj = new Object();
obj.name = '홍길동';
Object.prototype | 객체 | |||
---|---|---|---|---|
constructor | Object 생성자 함수 | name | '홍길동' | |
[[Prototype]] | Object.prototype |
=> 결과는 똑같지만 생성 과정이 다르다.
var foo = function() {};
console.log(foo.__proto__ === Function.prototype); // true
console.log(foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
console.log(foo.constructor === Function); // true
: 생성자 함수와 함께 생성됨
JS Engine의 작동이 시작되면 생성되는 함수
JS Engine이 작동되면 전역 객체(window 객체: 계층에서 최상위 객체)가 생성되고, built-in 함수 객체들은 전역 객체의 property로 등록된다.
function Person(name='default') {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`안녕하세요. ${this.name}`);
}; // 프로토타입 메서드 (생성자 함수 객체의 프로토타입의 property로 붙는 메서드)
// 인스턴스 메서드 : 생성자 함수 내에 property로 만들어지는 메서드
const me = new Person('홍길동');
me.sayHello(); // 안녕하세요. 홍길동 (prototype property 메서드를 호출)
me.sayHello = function() {
console.log(`Hello ${this.name}`);
};
me.sayHello();
Person.prototype = {
constructor: Person, // 직접 constructor를 연결
sayHello() {
console.log('안녕하세요!');
}
};
const me = new Person('홍길동');
console.log(me.constructor === Object);;
Person.prototype = {
// constructor: Person, // 직접 constructor를 연결 가능
sayHello() {
console.log('안녕하세요!');
}
};
const me = new Person('홍길동');
const parent = {
sayHello() {
console.log('안녕하세요!');
}
};
Object.setPrototypeOf(me, parent); // prototype 객체를 변경할 수 있는 메서드
// me의 prototype 객체를 parent로 설정
console.log(me.__proto__.constructor === Person.prototype); // false
function foo() {
x = 10; // ReferenceError가 발생해야 상식과 부합!
} // 묵시적(암묵적) 전역, implicit global => 전역변수화!
foo();
console.log(x); // 10
= Built-in 함수 (function)
= Built-in 전역함수
var obj = new Object();
var str = 'Hello'; // primitive value
// data type : string
str.toUpperCase(); // wrapper 객체 생성, 소멸
str.toLowerCase(); // wrapper 객체 생성, 소멸
// 객체의 생성과 소멸이 반복되기 때문에 literal
var strObj = new String('홍길동');
'Hello'.toUpperCase(); // primitive value에 메서드 호출 (원래는 에러)
// 그러나 JS 엔진은 wrapper 객체(new String('Hello'))를 묵시적으로 만든다.
// wrapper 객체(유사 배열 객체)는 String().prototype의 상속을 받기 때문에
// String.prototype의 메서드를 사용할 수 있다.
// 근데 String.prototype의 [[Prototype]]은 Object.prototype임
// console.log(typeof strObj); // object
console.dir(strObj); // 유사 배열 객체 (wrapper 객체)
- window (브라우저 환경)
- global (node 환경)
=> globalthis (ES11)
var obj = {};
function myFunc() {
console.log('Hello'); // 함수 코드
function sayHello() { // myFunc의 함수 코드
// nested function (inner function, 중첩 함수)
console.log('하이'); // sayHello의 함수 코드
}
}
myFunc(); // 전역 코드
=> 이러한 작업을 위해 전역 실행 컨텍스트를 생성
=> 이러한 작업을 위해 함수 실행 컨텍스트를 생성
const x = 1;
function foo() {
const y = 2;
function bar() {
const z = 3;
console.log(x + y + z);
}
bar();
}
foo();
// 클로저 예시
const x = 1;
function outer() {
const x = 10;
const inner = function() {
console.log(x);
}
return inner;
}
const innerFunc = outer();
innerFunc();
// innerFunc()가 호출되면 outer()가 호출되는데 inner가 return되면 지역변수 x=10, inner 함수는 사라져야 한다.
// 그러나 지역변수가 사라지지 않고 남아있어 innerFunc를 실행하면 10이 출력된다.
// 가비지컬렉터 : 메모리를 효율적으로 사용하기 위해 어떤 데이터를 아무도 참조하지 않으면 이 데이터를 삭제한다.
// inner 함수에서 x를 호출하면 이 x는 상위에 존재하는 (lexical) x의 value인 10을 출력하는데,
// inner 함수가 return 되어 innerFunc가 inner 함수를 참조하게 된다.
// 따라서 x와 inner의 참조가 아직 남아있기 때문에 가비지 컬렉션이 수행되지 않고 함수의 실행 컨텍스트가 종료되어도
// 데이터가 삭제되지 않음
function foo() {
const x = 1;
const y = 2;
function bar() { // 외부 함수의 식별자를 참조하지 않은 상태(3)
const z = 3;
console.log(z);
}
return bar;
}
const bar = foo(); // 중첩함수가 외부함수의 결과값으로 return되고 있지만 클로저는 아님
bar();
function foo() { // 중첩함수가 return 되지 않고(2), 생명 주기가 외부 함수보다 짧다(4).
const x = 1;
const y = 2;
function bar() {
console.log(x);
}
bar();
}
foo();
const increase = function() {
let num = 0;
return ++num;
}
console.log(increase());
console.log(increase());
console.log(increase());
const increase = (function() {
let num = 0;
return function() {
return ++num;
}
}());
console.log(increase());
console.log(increase());
console.log(increase());
const counter = (function() {
let num = 0;
return { // 함수가 포함된 것을 return하면 클로저 생성 가능
increase() { // 메서드1
return ++num;
},
decrease() { // 메서드2
return --num;
}
};
}());
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.increase()); // 3
console.log(counter.decrease()); // 2
console.log(counter.decrease()); // 1