__ proto__
property와 prototype
property__proto__
Object
생성자 함수에게 상속받는다.Object.prototype.__proto__
💡 상속을 표현할 때는 화살표방향을 아래에서 위를 가리키게 그린다.
function Person(name){
this.name = name; // instatnce의 property
}
const person = new Person('홍길동');
console.dir(Person); // 브라우저에서 확인
1️⃣ f Person(name) → prototype: {constructor: f} → [[Prototype]]: Object → proto: Object → proto: null
2️⃣ f Person(name) → [[Prototype]]: f () → [[Prototype]]: Object → proto: f ()→ [[Prototype]]: Object → proto: Object → proto: null
1️⃣, 2️⃣ 내용은 브라우저에서 확인할 수 있다.
💡 그런데
__proto__
라는 표현이 code에 직접 나오는 것은 권장되지 않는다.
혹시라도 상속을 못받는 경우 undefined가 뜨기 때문이다.
__proto__
보다는Object.getPrototypeOf()
를 사용하는 것이 좋다.
// 객체 생성하는 방법
// 객체를 생성할 때 객체의 상위 prototype 객체를 직접 지정할 수 있다.
const obj = Object.create(null);
console.log(obj.__proto__); //undefined
// 그럼 이런 경우에는 어떻게 하면 좋을까요?
// Object()가 가지고 있는 method를 이용하는게 좋다.
// Object.getPrototypeOf(obj): obj의 상위 프로토타입 객체를 들고와라!
console.log(Object.getPrototypeOf(obj)); //null -> 상위 프로토타입 객체가 없다.
prototype property
constructor
(함수 선언문, 함수 표현식, class를 생성자 함수로 사용했을 때)만 가질 수 있는 propertyproperty | 소유 | 의미 |
---|---|---|
proto | 모든 객체 | prototype 객체의 참조 |
prototype | constructor | prototype 객체의 참조 |
💡 그림을 code로 확인해보자.
var foo = function(){}; // 함수표현식 console.log(foo.__proto__ === Function.prototype); //true console.log(foo.prototype.__proto__ === Object.prototype); //true console.log(Object.prototype.__proto__); //null // 함수 foo의 생성자는 Function 생성자 함수 console.log(foo.constructor === Function); //true
객체 literal → Object.prototype → Object
함수 literal → Function.prototype → Function
배열 literal → Array.prototype → Array
Prototype 객체는 언제 생성되나요?
→ 생성자 함수와 같이 만들어져요.
/* instance 메소드와 prototype 메소드 */
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과 property shadowing */
// overriding: 상속되는 method를 재정의
me.sayHello = function(){
console.log(`Hello ${this.name}`);
}
// 만약 overriding이 발생하면 이 발생된 overriging에 의해서
// 숨겨진 prototype 메소드를 property shadowing 되었다고 한다.
me.sayHello(); //Hello 홍길동
function Person(name){
this.name = name
}
// 자동으로 만들어진 Person의 prototype을 객체literal을 사용해서 변경하기
Person.prototype = {
sayHello(){
console.log(('안녕하세요!'));
}
}
const me = new Person('홍길동');
console.log(me.constructor === Object); //true
// me의 생성자는 Person이 아닌 Object임을 주의할 것!
그런데 이렇게 변경을 하게되면 객체 literal로 만든 객체는 non-constructor
여서 me 객체는 constructor property가 없기 때문에 상위 객체인 Person과의 연결이 끊기게 된다. 그렇기 때문에 me의 constructor를 더 상위 객체에서 찾게된다. 따라서 me의 constructor는 Person이 아닌 Object가 된다.
만약 이런 문제가 발생하는 것이 싫다면 직접 변경한 객체에 constructor property를 추가해서 Person과 연결시켜주면 된다.
function Person(name){
this.name = name
}
// 자동으로 만들어진 Person의 prototype을 객체literal을 사용해서 변경하기
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
function Person(name){
this.name = name
// instance 메소드: 각각의 instance가 독자적으로 갖는 메소드
this.callme = function(){}
}
// prototype 메소드: 만들어진 하위 객체에 상속되는 메소드
Person.prototype.sayHello = function(){
console.log('안녕!');
}
// static 메소드: instance가 사용할 수 없는 메소드
Person.staticMethod = function(){ // person의 property에 붙는다.
console.log('Hi!');
}
function foo(){
x = 10;
// ReferenceError가 아닌 implicit global(묵시적 전역)이 발생
// 전역변수화! (window객체의 property로 붙는다.)
}
foo();
console.log(x); //10
// x가 window라는 전역 객체의 property로 붙어서 전역 변수처럼 사용할 수 있게된다.
🧐 이런 현상이 좋은건가요?
당연히 좋지 않다. 어디선가 side effect가 생길 확률이 높다. 이런 식의 code는 지양해야 한다.
이런 상황을 언어적인 차원에서 사용하지 못하게끔 처리할 수 있다. 어떻게요??
→ Strict mode
'use strict';
를 적어준다.
'use strict';
function foo(){
x = 10;
}
foo();
console.log(x); //ReferenceError: x is not defined
strict mode를 함수별로 사용할 수 있다.
(function(){
// non-strict mode
var let = 10; // 예약어인 let을 식별자를 써도 error가 발생하지 않는다.
// inner function, nested function
function foo(){
'use strict'; // strict mode
let = 20; //SyntaxError: Unexpected strict mode reserved word
}
}())
window
를 가리키는데 strict모드에서는 this가 undefined
가 된다.Built-in Object
, Built-in 함수(function)
, Built-in 전역함수
모두 같은 용어이다.
built-in (전역)함수
JavaScript Engine이 기동하면 생성된다.
💡 Math와 JSON은 생성자 함수가 아니라서 객체를 만들 수 없습니다! 대신 자체적인 정적 method를 제공합니다.
/* 자바스크립트의 최상위 객체는 window */
console.log(Object === window.Object); //true (브라우저에서 확인하기)
유사 배열 객체(wrapper 객체)
var obj = new Object();
var str = 'Hello'; // primitive value
// data type: string
'Hello'.toUpperCase(); // 내부적으로 refer 객체를 만든다.
str.toUpperCase(); // wrapper 객체 생성, 소멸(객체화시킨 후 대문자로 바꾸고 소멸된다.)
str.tolowerCase(); // wrapper 객체 생성, 소멸(객체 생성 소멸이 반복되므로 좋지않다.)
var strObj = new String('홍길동');
//console.log(typeof strObj); //object
console.dir(strObj); // 유사 배열 객체(브라우저에서 확인)
전역 객체(사용하는 platform에 따라 다르다.)
window
, global
→ globalThis
(ES11 표준)window
closure는 JavaScript 고유의 개념이 아니다. 함수형 프로그래밍 언어에서 제공되는 기능이다.
First-class citizen(object)(일급객체)
에 대한 이해 선행
💡 일급 객체
1. 객체가 익명으로 생성이 가능(runtime에 생성이 가능)
2. 값으로 변수나 자료구조에 저장이 가능
3. 함수의 매개변수로 전달 가능
4. 함수의 return값으로 사용 가능
“function” 함수 → 객체로 취급 → 일급객체 취급 → “first-class function”
MSN(MicroSoft Network)에서 closure를 간단하게 정의
→ closure는 함수와 그 함수가 선언된 lexical 환경(environment)의 조합이다.
💡
lexical environment
는 뭔가요?
execution context(실행 컨텍스트)
실행 컨텍스트 = 정보를 저장하기 위한 메모리 공간(lexical environment)으로 인식 + 코드 실행 순서 관리
메모리 공간 ⇒ lexical Environment
코드 실행 순서 관리 ⇒ Execution context Stack(stack으로 관리)
→ 4가지 종류의 코드
⇒ code가 어디에 있느냐에 따라 scope가 달라진다. scope에 따라 code를 관리해야 한다. 실행 컨텍스트가 별도로 생성되고 관리된다.
const x = 1;
function foo(){
const y = 2;
function bar() {
const z = 3;
console.log(x+y+z);
}
bar();
}
foo()
💡 closure 조건
const x = 1; function outer(){ const x = 10; const inner = function(){ console.log(x); } return inner; } const innerFunc = outer(); innerFunc();
- closure는 중첩함수
- 이 중첩함수가 외부함수의 결과값으로 return
- 리턴되는 중첩함수가 외부함수의 식별자를 참조
- 리턴되는 중첩함수의 life cycle(생명주기)가 외부함수보다 길어야 한다.
- 이 때 중첩함수에서 외부함수에 대한 참조가 남아있기 때문에 외부함수의 실행은 execution context stack에서 제거되지만 외부함수의 lexical environment는 메모리에 남아있어서 중첩함수에 의해 사용될 수 있는 현상
이 조건에 맞는 것들만 closure이다.
아래는 closure가 아닌 예시이다.function foo(){ const x = 1; const y = 2; function bar(){ const z = 3; console.log(z); } return bar; } const bar = foo(); bar();
function foo(){ const x = 1; const y = 2; function bar(){ const z = 3; console.log(x); } bar(); } const bar = foo(); bar();
→ Information Hiding
구현에 사용한다!
let num = 0; // 변수 num은 전역이기 때문에 보호할 수 없다.
const increase = function(){
return ++num;
}
console.log(increase()); //1
console.log(increase()); //2
num = 100; // num 데이터 손상
console.log(increase()); //101
변수를 보호하고(숨기고) 싶다.. → 전역 변수 num을 지역 변수로 만들자.
const increase = function(){
let num = 0;
return ++num;
}
console.log(increase()); //1
console.log(increase()); //1
console.log(increase()); //1
함수를 호출할 때마다 초기화가 되는 문제가 발생한다..😧
💡 함수 본연의 기능을 살리면서 변수를 보호하는 방법은 없을까??
→ closure를 응용하자!const increase = (function(){ // 즉시 실행 함수 let num = 0; return function(){ return ++num; } }()); console.log(increase()); //1 console.log(increase()); //2 console.log(increase()); //3
변수 num에 접근할 수도 없고, 함수 본연의 기능도 잘 유지된다.
const counter = (function(){ let num = 0; return { increase(){ return ++num; }, decrease(){ 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 console.log(counter.decrease()); //0
information hiding을 closure를 통해 구현한 예시