자바스크립트 제대로 배우자3

2023. 4. 3. 00:50Javascript/실전 자바스크립트

섹션 6. 제너레이터

제너레이터 1 (iterable, iterator)

함수의 실행을 중간에 멈추고, 재개할 수 있는 독특한 기능

실행을 멈출 때 값을 전달할 수 있기 때문에, 반복문에서 제너레이터가 전달하는 값을 하나씩 꺼내서 사용할 수 있다.

이는 배열이 반복문에서 사용되는 방식과 같음

다만 제너레이터는 배열과 달리 값을 미리 만들어 놓지 않는다. (값을 미리 만들어놓으면, 불필요하게 메모리를 사용하는 단점이 있기에)

제너레이터를 사용하면, 필요한 순간에 값을 계산해서 전달할 수 있기 때문에 메모리 측면에서 효율적

 

*와 함께 정의된 함수와 함수가 반환하는 제너레이터 객체로 구성이 된다.

제너레이터 함수 안에서 yield 키워드를 사용하면, 함수의 실행을 멈출 수 있다.

function* f1() {
  yield 10;
  yield 20;
  return 'finished';
}
const gen = f1();

제너레이터 객체는 next라는 메서드를 갖고있다.

const gen = f1(); 함수를 호출 할 때에, 위의 함수가 전혀 실행이 되지않는다. >> 아무것도 출력되지않음

gen객체가 생성이 되고, next 메서드를 호출해야 실행이 되는 것.

yield를 만날 때까지만 실행을 한다. yield 10;에서 실행을 멈춘다.

 

첫번째 next를 실행시키면

f1-1

{ value: 10, done: false }

가 출력된다.

 

두번째를 실행하면, 멈췄던 곳에서 실행을 하게 되는데, 또 동일하게 yield 20;에서 실행을 멈춘다.

 

그리고 세번째 동일, & done은 true가 되었다.

return 키워드는 done을 true로 만드는 효과가 있다.

function* f1() {
  console.log('f1-1');
  yield 10;
  console.log('f1-2');
  yield 20;
  console.log('f1-3');
  return 'finished';
}
const gen = f1();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
/*
f1-1
{ value: 10, done: false }
f1-2
{ value: 20, done: false }
f1-3
{ value: 'finished', done: true }
*/

근데 아래와 같이 코드를 변경을 하면, 아래와 같이 출력이 된다.

첫번째부터 계속 done은 true

value가 undefined가 되었기 때문에, 뒤에부분이 실행되지않는다는 것을 알 수 있다.

 

제너레이터는 제너레이터 객체를 만들어서, next를 호출하면서 진행을 하게 되는데, 제너레이터가 next를 가지고 있다는 것은 제너레이터 객체가 iterator라는 것을 암시한다.

function* f1() {
  if (true) {
    return "finished";
  }
  console.log("f1-1");
  yield 10;
  console.log("f1-2");
  yield 20;
  console.log("f1-3");
}
const gen = f1(); // 제너레이터 객체 생성
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

/*
{ value: 'finished', done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/

generate객체에는 return메서드가 있다.

return 메서드를 호출하면, 반환되는 객체의 done속성값은 true가 된다.

return에서는 그 뒤를 실행하는것이 아니라, 멈췄던 곳에서 실행을 그대로 종료하는 것

그리고 입력한 값을 value에 넣고, done은 true가 됨

그 이후에 아무리 next를 호출해도 value는 undefined, done은 true

function* f1() {
  console.log('f1-1');
  yield 10;
  console.log('f1-2');
  yield 20;
  console.log('f1-3');
  return 'finished';
}

const gen = f1();
console.log(gen.next());
console.log(gen.return('abc'));
console.log(gen.next());
/*
f1-1
{ value: 10, done: false }
{ value: 'abc', done: true }
{ value: undefined, done: true }
*/

throw메서드는 예외가 발생한 것으로 인식

try catch문으로 예외처리

그래서 throw 문 발생시, catch문으로 !

그래서 30을 출력하고, 그 다음 next시, 거기부터 다시 시작

function* f1() {
  try {
    console.log('f1-1');
    yield 10;
    console.log('f1-2');
    yield 20;
  } catch (e) {
    console.log('f1-catch', e);
    yield 30;
    console.log('f1-3');
    yield 40;
    console.log('f1-4');
  }
}
const gen = f1();
console.log(gen.next());
console.log(gen.throw('some error'));
console.log(gen.next());
console.log(gen.next());
/*
f1-1
{ value: 10, done: false }
f1-catch some error
{ value: 30, done: false }
f1-3
{ value: 40, done: false }
f1-4
{ value: undefined, done: true }
*/

제너레이터 객체는 iterable이면서 iterator이다

다음 조건을 만족하는 객체는 iterator이다.

 

iterator 조건

1. next 메서드를 가지고있다.

2. next 메서드는 value와 done 속성값을 가진 객체를 반환

3. done속성값은 작업이 끝났을 때 참이 된다.

 

iterable 조건

1. Symbol.iterator속성값으로 함수를 갖고있다.

2. 해당함수를 호출하면 iterator를 반환한다.

 >> 반복 가능한 객체

 

배열은 대표적인 iterable!

const arr = [10, 20, 30];
const iter = arr[Symbol.iterator](); // 배열은 Symbol.iterator라는 속성값이 있다.
console.log(iter.next()); // 그 함수를 호출하면 iterator가 반환이 된다

//{ value: 10, done: false }

 

그래서 다음과 같이 반복해서 next를 부르면 다음과 같이 배열의 아이템이 반환된다.

const arr = [10, 20, 30];
const iter = arr[Symbol.iterator](); // 배열은 Symbol.iterator라는 속성값이 있다.
console.log(iter.next()); // 그 함수를 호출하면 iterator가 반환이 된다
console.log(iter.next());
console.log(iter.next());
console.log(iter.next()); 
/*
{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }
*/

이코드는 generator 객체가 iterable이라는 것을 보여준다.

여기서 gen 인 제너레이터 객체를 만들었고, 그 안에 Symbol.iterator라는 속성값이 있다.

함수를 호출하면 자기 자신이 되는데, 제너레이터 객체는 iterator라고 했다.

Symbol.iterator 호출시, iterator가 반환이 되었기 때문에 

제너레이터 객체는 iterator이면서, 동시에 iterable이다.

 

function* f1() {
  // ...
}
const gen = f1();
console.log(gen[Symbol.iterator]() === gen);
// true

for of와 전개 연산자(...f1())에서 유용하게 쓰인다.

of 오른쪽에 iterable을 입력할 수 있다.

내부적으로 iterable로부터 iterator를 얻을 수 있다.

iterator의 next 메서드를 호출하면서, done 속성 값이 참이 될 때가지 반복할 것이다.

 

... 또한!! 참될때까지 반복!

function* f1() {
  yield 10;
  yield 20;
  yield 30;
}
for (const v of f1()) {
  console.log(v);
}
const arr = [...f1()];
console.log(arr);
/*
10
20
30
[ 10, 20, 30 ]
*/

제너레이터 2

map, filter, take와 같은 함수들을 구현가능

세 함수 모두 제너레이터 함수로 구현했고, iterable을 입력받고있다.

세 함수는 제너레이터 덕분에 새로운 배열 객체를 생성하지않는다.

일반적으로, map 또는 filter를 ㅏ용하면, 새로운 배열 객체가 새엉이 되어서 반환이 되는데

하지만 아래의 함수들은 새로운 배열 객체를 생성하지않기에, 메모리를 좀 더 효율적으로 ㅅㅏ용할 수 있다.

>> 연산이 필요한 순간에만 실행된다!!!

 

제너레이트 함수 실행하면, 제너레이터 객체만 생성되고, 값이 필요한 순간에만(console.log([...result]);) iterable을 통해 필요한 연산을 수행한다.  >> 필요한 순간에만 연산하는 것을 지연평가(lazy evaluation)라고 부름

function* map(iter, mapper) {
  for (const v of iter) {
    yield mapper(v);
  }
}

function* filter(iter, test) {
  for (const v of iter) {
    if (test(v)) {
      yield v;
    }
  }
}

function* take(n, iter) {
  for (const v of iter) {
    yield v;
    if (--n <= 0) return;
  }
}

const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = take( //iterable에서 n개 만큼만 가져오는것!
  3, //아래로 나온것들로부터 3개만 가져옴
  map(
    filter(values, n => n % 2 === 0), //짝수 필터링
    n => n * 10,
  ),
);
console.log([...result]); //[ 20, 40, 60 ]

그리고, take에 의해서 3개까지만 빼내면되기에, 실제 콘솔로 찍어보면 6가지만 찍히는것을 확인할 수 있다.

function* map(iter, mapper) {
  for (const v of iter) {
    yield mapper(v);
  }
}

function* filter(iter, test) {
  for (const v of iter) {
    console.log(v)
    if (test(v)) {
      yield v;
    }
  }
}

function* take(n, iter) {
  for (const v of iter) {
    yield v;
    if (--n <= 0) return;
  }
}

const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = take( //iterable에서 n개 만큼만 가져오는것!
  3, //아래로 나온것들로부터 3개만 가져옴
  map(
    filter(values, n => n % 2 === 0), //짝수 필터링
    n => n * 10,
  ),
);
console.log([...result]); //[ 20, 40, 60 ]

/*
1
2
3
4
5
6
[ 20, 40, 60 ]
*/

필요한 연산만 하면되기에, 값을 무한대로 표현하는 것도 가능하다

실제로, 일반함수로 표현하면, 프로그램은 먹통이 되었겠지만

제너레이터이기에 딱 필요한 부분까지만 하니 정상적으로 작동한다.

function* map(iter, mapper) {
  for (const v of iter) {
    yield mapper(v);
  }
}
function* filter(iter, test) {
  for (const v of iter) {
    if (test(v)) {
      yield v;
    }
  }
}
function* take(n, iter) {
  for (const v of iter) {
    if (n <= 0) return;
    yield v;
    n--;
  }
}

function* naturalNumbers() {
  let v = 1;
  while (true) {
    yield v++;
  }
}

const values = naturalNumbers();
const result = take(
  3,
  map(
    filter(values, n => n % 2 === 0),
    n => n * 10,
  ),
);
console.log([...result]);
// [ 20, 40, 60 ]

제너레이터 함수에서 다른 제너레이터 함수를 호출할 때는 yeild* 키워드를 이용하면된다.

function* g1() {
  yield 2;
  yield 3;
}
function* g2() {
  yield 1;
  yield* g1();
  yield 4;
}
console.log(...g2());
// 1 2 3 4

yeild* 옆에는 iterable이면 무엇이든 올 수 있다.  배열 또한!!!  (yield* [2, 3, 6 ,7];)

function* g1() {
  yield 2;
  yield 3;
}
function* g2() {
  yield 1;
  yield* [2, 3, 6 ,7];
  yield 4;
}
console.log(...g2());
// 1 2 3 6 7 4

 아래의 두 함수는 동일한 것이다. 그러니까 yeild* [2,3[이 for of와 같다는것!

function* g1() {
  yield 1;
  yield* [2, 3];
  yield 4;
}

function* g2() {
  yield 1;
  for (const value of [2, 3]) {
    yield value;
  }
  yield 4;
}

제너레이터 함수는 외부로부터 데이터를 받아서 사용할 수 있다.

여기서는 yield가 값을 반환하는 것처럼 표현을 했는데, next인수로 값을 넣으면 yield의 반환값이 된다.

근데 첫번째 next는 제너레이터 함수의 첫번째 yeild를 만날 때 까지 실행을 한다. 그래서 첫번째 인수는 아무런 역할을 하지않는다.

그다음 next의 10은 첫번째 멈춰있던 yield의 반환값이 된다. 그래서 data1이 10!!!

data2는 20

function* f1() {
  const data1 = yield;
  console.log(data1);
  const data2 = yield;
  console.log(data2);
}
const gen = f1();
gen.next();
gen.next(10);
gen.next(20);
/*
10
20
*/

generator함수는 다른 함수와 협업 멀티테스킹(cooperative multitasking)을 할 수 있다.

멀티테스킹 : 여러개의 태스크를 실행할 때 하나의 태스크가 종료되기 전에 멈추고, 다른 태스크가 실행되는 것

 

제너레이터는 실행을 멈추고 재개할 수 있기 때문에 멀티태스킹이 가능하다

 협업이라는 단어가 붙는 이유는 제너레이터가 실행을 멈추는 시점을 자발적으로 선택하기 때문이다!

 

반대로 실행을 멈추는 시점을 자발적으로 선택하지못하면, 선점형(preemptive) 멀티태스킹이라고 부르면된다.

일반적 OS에서는 선점형 멀티태스킹을 사용하는데,

OS에서 세개의 프로세스가 있다고 하면, OS는 한순간에 하나의 프로세스를 일정시간만큼만 실행을 한다. 그리고 일정시간이지나면 그 프로세스의 실행을 종료한다.  강제로 실행을 종료하기때문에 OS에서는 선점형 멀티태스킹을 사용한다고 할 수 있다.

제너레이터 함수는 yeild 키워드를 통해서 자발적으로 자신의 실행을 멈춘다.

제너레이터 함수와 일반함수의 협업을 위해 아래의 코드를 보자.

첫번째 next 메서드에 입력하는 인수는 아무런 의미가 없기에 빈 문자열로 입력을 하였고,

제너레이터 함수에서는 '안녕 나는 민수야' 값이 반환이 된다.

 

그리고 두번째 next에서는 '안녕 나는 수지야'가 인수로 입력이된다.

function* minsu() {
  const myMsgList = [
    '안녕 나는 민수야',
    '만나서 반가워',
    '내일 영화 볼래?',
    '시간 안 되니?',
    '내일모레는 어때?',
  ];
  for (const msg of myMsgList) {
    console.log('수지:', yield msg);
  }
}

function suji() {
  const myMsgList = ['', '안녕 나는 수지야', '그래 반가워', '...'];
  const gen = minsu();
  for (const msg of myMsgList) {
    console.log('민수:', gen.next(msg).value);
  }
}
suji();
/*
민수: 안녕 나는 민수야
수지: 안녕 나는 수지야
민수: 만나서 반가워
수지: 그래 반가워
민수: 내일 영화 볼래?
수지: ...
민수: 시간 안 되니?
*/

제너레이터 함수에서 예외를 처리하는 방법을 알아보자

throw new Error(~~)와 같이 예외를 발생하면, next함수에 영향을 준다.

여기서는 무조건 적으로 예외가 발생하기에, incatch가 출력이 된다.

function* genFunc() {
  throw new Error('some error');
}
function func() {
  const gen = genFunc();
  try {
    gen.next();
  } catch (e) {
    console.log('in catch');
  }
}
func();
//in catch

 

 

섹션 7. 프로토타입

프로토타입 1

getPrototypeOf로 프로토타입을 가져올 수 있다.

프로토타입은 null 또는 object 타입인데, 현재는 object가 들어있다.

많은 자바스크립트 엔진에서는 __proto__라는 이름으로, 프로토타입 속성에 접근할 수 있다.

자바스크립트 표준에서는 브라우저에서만 __proto__를 지원하는 것으로 나와있지만, 

사실 모든 자바스크립트 엔진에서 지원한다.

하지만 프로토타입에 접근하는 안전하고 공식적인방법은 getPrototypeOf 함수를 사용하는 것이다.

const person = {
  name: 'mike',
};
const prototype = Object.getPrototypeOf(person);
console.log(typeof prototype);
console.log(person.__proto__ === prototype);
/*
object
true

*/

프로토타입 변경을 위해서는 setPrototypeOf 함수를 사용할 수 있다.

programmer.__proto__ = person; 에서보면, __proto__에 할당하는 것과 같다.

그럼 programmer의 프로토타입은 person이 될것이다.

 

console.log(Object.setPrototypeOf(programmer, person)); 하면, { language: 'javascript' }가 출력된다.

프로토타입은 속성값을 읽을 때 사용이된다.

const person = {
  name: 'mike',
};
const programmer = {
  language: 'javascript',
};

Object.setPrototypeOf(programmer, person);
// programmer.__proto__ = person;
console.log(Object.getPrototypeOf(programmer) === person);
console.log(programmer.name);
/*
true
mike
*/

프로토타입을 여러단계로 연결할 수도 있다.

programmer을 person과 연결하고, frontendDev를 programmer와 연결학

그러면 frontendDev의 프로토타입은 programmer이고 그 프로토타입은 person이라는 것이다.

const person = {
  name: 'mike',
};
const programmer = {
  language: 'javascript',
};
const frontendDev = {
  framework: 'react',
};

Object.setPrototypeOf(programmer, person);
Object.setPrototypeOf(frontendDev, programmer);
console.log(frontendDev.name, frontendDev.language); //1번
//2번
console.log( 
  frontendDev.__proto__.__proto__.name,
  frontendDev.__proto__.language,
);
//1번과 2번은 동일하다
/*
mike javascript
mike javascript
*/

이렇게 새로운 속성을 추가할때, 프로토타입 체인을 이용하는 것이 아니고, 자기자신에게 속성을 추가한다.

programmer.name = 'jane';

그래서 출력해보면 각자의 값을 유지하고있는 것이다.

const person = {
  name: 'mike',
};
const programmer = {
  language: 'javascript',
};

Object.setPrototypeOf(programmer, person);
programmer.name = 'jane'; //자기자신에게 속성추가
console.log(programmer.name);
console.log(person.name);
/*
jane
mike
*/

프로토타입은 일반적인 객체이기때문에, sayHello처럼 함수를 정의해서 공통로직을 추가할 수 있다.

programmer에게는 이 함수가 없지만, 아래와 같이 함수를 호출할 수 있다.

const person = {
  name: 'mike',
  sayHello() {
    console.log('hello!');
  },
};
const programmer = {
  language: 'javascript',
};

Object.setPrototypeOf(programmer, person);
programmer.sayHello();
// hello!

아래와 같이 for in 문법을 사용하면, 프로토타입에 있는 속성까지 사용된다.

혹, 자신만의 속성만을 사용하고 싶다면, hasOwnProperty 라는 메서드 또는 Object.keys 함수를 사용하면된다.

const person = {
  name: 'mike',
};
const programmer = {
  language: 'javascript',
};

Object.setPrototypeOf(programmer, person);
for (const prop in programmer) {
  console.log(prop);
}
/*
language
name
*/

for (const prop in programmer) {
  if (programmer.hasOwnProperty(prop)) {
    console.log(prop); //language
  }
}

for (const prop of Object.keys(programmer)) {
  console.log(prop); //language
}

프로토타입 2

new키워드를 사용해 만드는 함수를 생성자 함수라고 부른다. 첫번째 문자는 대문자!

new키워드를 사용해 함수를 실행하면, 자바스크립트 엔진은 내부적으로 this에 빈객체를 할당해준다.

그리고 함수가 종료하기 직전에 그 this를 반환해준다.

 

아래에서 const 로 선언된 person은 this이다!!

function Person(name) {
  // this = {};
  this.name = name;
  // return this;
}

const person = new Person('mike');
console.log(person.name);
// mike

자바스크립트의 기본타입은 이런생성자 함수를 가지고있다.

하지만 이러한 생성자함수로 해당값을 생성할 필요는 없다.

각각의 리터럴 문법을 이용하면된다.

const obj = new Object({ a: 123 });
const arr = new Array(10, 20, 30);
const num = new Number(123);
const str = new String('abc');

console.log({ obj, arr, num, str });
/*
{
  obj: { a: 123 },
  arr: [ 10, 20, 30 ],
  num: [Number: 123],
  str: [String: 'abc']
}
*/

모든 함수는 prototype 속성을 가지고있다.

그리고 이렇게 new 키워드로 생성된 객체의 프로토타입(const person = new Person('mike');)은 그 생성자 함수의 prototype속성(Person.prototype)을 가리킨다.

 

prototype의 속성은 이러한 객체를 가리킨다. 왼쪽아래에 있는

함수의 prototype과 prototype의 속성은 다르다!

[[Prototype]]은 모든 객체가 가지고있는 프로토타입이다.

함수의 proototype은 함수에만 있는 특별한 속성이다.

new 키워드를 이용해서 만들어진 객체는 객체의 프로토타입을 보면, 자신을 만들 때 사용한 그 생성자 함수의 프로토타입 객체를 가리킨다.

function Person(name) {
  this.name = name;
}
const person = new Person('mike');

console.log(Person.prototype);
console.log(Object.getPrototypeOf(person) === Person.prototype);
/*
{}
true
*/

함수의 prototype과 prototype의 속성은 다르다!

new 키워드를 이용해서 만들어진 객체는 객체의 프로토타입을 보면, 생성자 함수의 prototype 속성과 같다.

함수의 prototype 속성이라는 것은 객체이다. 객체의 프로토타입을 보면 Object 생성자 함수의 prototype 속성과 같다.

그리고 Person이라는 생성자 함수는 함수이기때문에, 그 프로토타입은 Function이라는 생성자 함수의 prototype 속성과 같다.

Object 생성자 함수도 함수이기 때문에 그 프로토타입은 Function이라는 생성자 함수의 프로토타입 객체를 가리킨다.

그리고, Function이라는 생성자 함수의 prototype속성은 객체이기 때문에, 객체의 프로토타입을 보면, Object 생성자 함수의 prototype속성을 가리킨다.

Object 생성자 함수의 prototype 속성은 마찬가지로 객체이지만 조금 특별하게도, 그 프로토타입은 null을 가리킨다.

그래서 프로토타입 체인에서 가장 마지막을 담당한다.

function Person(name) {
  this.name = name;
}
const person = new Person('mike');

console.log(Object.getPrototypeOf(Person) !== Person.prototype);

console.log(Object.getPrototypeOf(person) === Person.prototype);
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Person) === Function.prototype);

console.log(Object.getPrototypeOf(Object) === Function.prototype);
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
/*
true
true
true
true
true
true
true
*/

Object의 프로토타입 객체는 프로토타입으로 null을 가리키는데, 

모든 프로토타입 체인은 Object의 프로토타입  객체로 모이고 있다. 그리고 최종적으로 null을 가리키고있다.

그래서 이 Object의 프로토타입 객체는 프로토타입 체인에서 마지막을 담당한다.

 

이건 그냥 내가 해봄..ㅎ

function Person(name) {
  this.name = name;
}
const person = new Person('mike');
console.log(Person); //[Function: Person]
console.log(Person.prototype); //{}
console.log(person); //Person { name: 'mike' }
console.log(person.prototype); //undefined
console.log(Object); //[Function: Object]
console.log(Object.prototype); //[Object: null prototype] {}
console.log(Function); //[Function: Function]
console.log(Object.prototype); //[Object: null prototype] {}
console.log(Function.prototype); //{}
console.log(Object.getPrototypeOf(Person)); //{}
console.log(Object.getPrototypeOf(person)); //{}

기본타입을 리터럴 문법으로 만들어도, 프로토타입은 해당 생성자 함수의 프로토타입 객체를 가리킨다.

number와 string은 객체가 아니지만, 속성에 접근하는 문법을 사용하면, 임시로 객체처럼 동작한다.(num.toFixed)

임시로 만들어진 프로토타입은 해당 생성자 함수의 프로토타입 속성을 참조한다.

 

num.toFixed와 같이 임시객체는 number의 프로토타입을 가리킬 것이다. Number.prototype.toFixed

그래서 마지막으로 출력된 것을 보면 메서드는 number의 프로토타입을 실행한 결과가 출력이된다.

const obj = {};
const arr = [];
const num = 123.456;
const str = 'abc';
console.log(Object.getPrototypeOf(obj) === Object.prototype);
console.log(Object.getPrototypeOf(arr) === Array.prototype);
console.log(Object.getPrototypeOf(num) === Number.prototype);
console.log(Object.getPrototypeOf(str) === String.prototype);
console.log(num.toFixed === Number.prototype.toFixed);
console.log(num.toFixed());
/*
true
true
true
true
true
123
*/

 

함수의 프로토타입 객체를 수정할 수도 있다.

이전에 만들었던 프로토타입(person1)은, 여전히 예전의 프로토타입 객체를 참조할 것이고, 

새로 만들어진것의 프로토타입(person2)은 수정된 프로토타입 객체를 참조할 것이고, 

function Person(name) {
  this.name = name;
}
const person1 = new Person('mike');

const newPrototype = {
  values: [],
  push(value) {
    this.values.push(value);
  },
  getValues() {
    return this.values;
  },
};
Person.prototype = newPrototype;
const person2 = new Person('jane');
console.log(Object.getPrototypeOf(person1) !== newPrototype);
console.log(Object.getPrototypeOf(person2) === newPrototype);

person2.push(1);
person2.push(2);
console.log(person2.getValues());
/*
true
true
[ 1, 2 ]
*/

생성자 함수 내에서 함수를 정의하면,

생성자 함수를 이용해서 객체를 만들 때마다 위에있는 두 함수가 생성이 된다.

그래서 메모리 측면에서 비효율적이다.

function Person(name) {
  this.name = name;
  this._salary = 0;
  this.setSalary = function (salary) {
    this._salary = Math.max(0, Math.min(1000, salary));
  };
  this.getSalary = function () {
    return this._salary;
  };
}
const person1 = new Person('mike');
person1.setSalary(2000);
console.log(person1.getSalary());

const person2 = new Person('jane');
console.log(person1.getSalary !== person2.getSalary);
/*
1000
true
*/

이럴때는 프로토타입 객체를 이용해서 함수를 정의하는 것이 좋다.

이러면 함수를 한번만 만들어서 재사용을 할 것이다.

function Person(name) {
  this.name = name;
  this._salary = 0;
}
Person.prototype = {
  setSalary(salary) {
    this._salary = Math.max(0, Math.min(1000, salary));
  },
  getSalary() {
    return this._salary;
  },
};
const person1 = new Person('mike');

person1.setSalary(2000);
console.log(person1.getSalary());

const person2 = new Person('jane');
console.log(person1.getSalary === person2.getSalary);
/*
1000
true
*/

함수의 프로토타입에는 constructor라는 속성이 있다.

constructor 그 함수를 가리킨다.

그럼 Person.constructor는? >> 이전에 입력한 값이 없기에 Person에는 constructor가 없기 때문에, 프로토타입에 가서 그 값을 찾을것이다. (Person.__proto__) !! 이것!

Person은 함수이기 대문에, 이 constructor는 Function 생성자 함수를 가리킬 것이다!

function Person(name) {
  this.name = name;
}

console.log(Person.prototype.constructor === Person);
console.log(Person.constructor === ?);

생성자함수를 모르는상태에서 constructor를 사용해서, 같은 객체를 생성할 수 있다.

makeInstanceOf에서의 obj가 무엇으로 만들어진것인지 알수가 없는데, 하지만 그 안에있는 constructor 속성이 그 오브젝트의 생성자 함수를 가리킨다. 그래서 이런식으로 똑같은 것을 생성할 수있다.(new obj.constructor())

newInst의 makeInstanceOf에 animal이든, person이든 내부적으로는 해당하는 생성자 함수를 이용하게 된다.

function Person() {
  this.sayHello = function () {
    console.log('hello');
  };
}
function Animal() {
  this.sayHello = function () {
    console.log('hm...');
  };
}
function makeInstanceOf(obj) {
  return new obj.constructor();
}
const person = new Person();
const animal = new Animal();
const newInst = makeInstanceOf(person);
newInst.sayHello();
// hello
class Person {
  constructor() {
    console.log(`Person's constructor`);
  }
  static id = 'abc';
}
class Programmer extends Person {}

console.log(Object.getPrototypeOf(Programmer) === Person);

const person = new Person();
// Person.constructor();
// console.log(Person.constructor === Function);
// console.log(new person.constructor());

console.log(Object.getPrototypeOf(Person) === Function.prototype);
console.log(Person.prototype);

console.log(Programmer.prototype);
console.log(Programmer.__proto__);
console.log(Person.prototype);
console.log(Object.getPrototypeOf(Programmer) === Person);
/*
true
Person's constructor
true
{}
Person {}
[class Person] { id: 'abc' }
{}
true
*/