[JS]생성자함수 & 프로토타입

JH Cho·2022년 9월 18일
0

모던JS DeepDive 공부

목록 보기
26/27

생성자함수(constructor function)

  • 생성자함수 기본 동작.
function 기계(){
  this.name = 'Kim';
  this.age = 15;
} // 컨스트럭터

var 학생1 = new 기계(); //오브젝트 생성됨.
console.log(학생1) //{name:'Kim', age:15}
  • 생성자 함수에 메서드 추가 가능.
function 기계(){
  this.name = 'Kim';
  this.age = 15;
  this.sayHi = function(){
     console.log('안녕하세요 '+this.name +'입니다.')
  }
}

var 학생1 = new 기계();
학생1.sayHi()//"안녕하세요 Kim입니다."
  • 파라미터 설정
function 기계(name, age){
  this.name = name;
  this.age = age;
  this.sayHi = function(){
     console.log('안녕하세요 '+this.name +'입니다.')
  }
}
var 학생1 = new 기계("park", 20);

🌼 참고!!< [인스턴스](https://velog.io/@bigwave-cho/ES6-this-arrow-function#:~:text=%ED%95%A8%EC%88%98%20%EC%95%88%EC%97%90%20%EC%93%B0%EB%A9%B4%20%EC%83%88%EB%A1%9C%20%EC%83%9D%EC%84%B1%EB%90%98%EB%8A%94%20obj%EB%A5%BC%20%EA%B0%80%EB%A6%AC%ED%82%B4.(%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4) > 컨스트럭터 내에서의 this는 인스턴스(컨스트럭터에서 생성되는 새로운 객체)를 가리킨다.

간단한 응용

상품 객체 찍어내기

function 기계(name, price){
  this.name = name;
  this.price = price;
}

var 상품1 = new 기계('shirts', 50000)
var 상품2 = new 기계('pants', 60000)
  • 내부세()라는 함수를 컨스트럭터 내에 위치시켜서
    상품가격 *10% 의 부가세 금액이 콘솔에 출력되도록 해보자
function 기계(name, price){
  this.name = name;
  this.price = price;
  this.부가세 = function(){
    console.log(price*0.1)
  }
}

var 상품1 = new 기계('shirts', 50000)
var 상품2 = new 기계('pants', 60000)

상품1.부가세()

Prototype

컨스트럭터와 그로 인해 만들어진 하위 객체 간 관계를 “부모 - 자식”으로 부른다.(상속)

function 기계(name, age){
  this.name = name;
  this.age = age;
  this.sayHi = function(){
    console.log('안녕 '+ this.name)
  }
}

기계.prototype.gender = '남';
//컨스트럭터 만들면 자동적으로 prototype이란 프로퍼티도 함께 생김.
//프로토타입은 부모의 유전자라고 생각하면 됨.

console.log(학생1.gender)//'남'

어떻게 이렇게 되니?

생성자 함수로 객체를 만들게 되면 생성자 내의 프로퍼티들도 그대로 복사된다.
하지만 prototype 객체에 넣어둔 프로퍼티들은 자식 객체에 복사되지 않는다.
자식 객체에서도 부모 객체의 프로퍼티를 불러올 수 있는데 이를 프로토타입 체인이라고 한다.

배열, 객체, 스트링 등등도 마찬가지로 전역객체의 메서드인 생성자 함수이다.

let arr = Array(1,2,3)
console.log(arr)// [1,2,3]

보통
let arr2 = [1,2,3] 이렇게 만들지만
JS엔진 입장에서는 알아서 생성자함수를 통해 만들어지는 과정을 거친다.

arr.forEach() 이런것들도 다 Array의 메서드.

결국 배열이나 객체 등을 선언하면 위와 같은 생성자 함수를 통해서 만들어지는 것이기 때문에
prototype을 체이닝하여 해당 메서드 들을 사용할 수 있게 되는 것이다.

프로토타입과 컨스트럭터 상속의 차이?

자식들이 직접 값을 소유하고 있게 하고싶으면 컨스트럭터

부모가 가지고 있다가 필요할 때 사용하게 하고싶으면 프로토타입으로.

보틍 함수들은 프로토타입으로 저장해 놓는 경우가 많다.

프로토타입의 특징

1. 프로토타입은 컨스트럭터 함수에만 생성된다.

2.부모의 프로토타입 확인하는 방법?

**proto”**

console.log(학생1.__proto__)

//콘솔창
[object Object] {
  gender: "남"
}

3. 오브젝트 간 상속 부여

더 좋은 ES5 문법 있기에
이해하기 위해 사용해보기

var 부모 = {name :'Kim'};
var 자식 = {};

자식.__proto__ = 부모;

console.log(자식.name)//"kim"

4. 기계의 부모?

콘솔창에서 알려주는 prototype chain

기계.__proto__ //Object.prototype

JS의 오브젝트, 어레이, 펑션 등은 모두 Object를 통해서 만들어진다.

연습해보자

function 출석부(name,age){
  this.name = name;
  this.age = age;
  this.sayHi = function(){
    console.log(`안녕 나는 ${this.name}입니다.`)
  }
}

var 학생1 = new 출석부('Kim',20);
var 학생2 = new 출석부('Park',21);
var 학생3 = new 출석부('Lee',22);
function Parent(){
  this.name = 'Kim';
}
var a = new Parent();

a.__proto__.name = 'Park';
console.log(a.name)

부모의 this.name = 'park'가 아니라
a의 부모의 프로토타입에 name : 'Park'를 추가하라는 뜻이고
또한 a 자식은 부모로부터 name : 'Kim'이라는 프로퍼티를 받은 상태이기 때문에
JS 바로 a의 name을 출력하고 부모의 프로터피까지 찾으러 올라가지 않는다.

이해를 돕기 위한 예시

function Parent(){
  age = 20;
}
var a = new Parent();

a.__proto__.name = 'Park';
console.log(a.name) // 'Park'
function Student(이름, 나이){
  this.name = 이름;
  this.age = 나이;
}

Student.prototype.sayHi = () => {
    console.log('안녕 나는 ' + this.name + '이야');
  }
var 학생1 = new Student('Kim', 20);

학생1.sayHi(); //왜 이 코드가 제대로 안나오죠?

화살표 함수의 경우 상위 객체의 this.name을 가져오기 때문이다.

다시 말하자면

오브젝트 내의 this는 해당 오브젝트를 가리키지만

이를 애로우 펑션에서 쓰면 객체 밖의 this를 가져와서 사용한다.

모든 어레이에서 3을 제거하는 함수 만들어보기

var arr = [1,2,3];
arr.remove3();

console.log(arr); //[1,2]

내가 푼 답.

var arr = [1,2,3];

Array.prototype.remove3 = function(array){
  let newArr = [];
  arr.forEach(item =>{
    if(item !== 3){
      newArr.push(item)
    }
  })
  console.log(newArr)
}

arr.remove3()

this를 이용해보면?

var arr = [1,2,3];

Array.prototype.remove3 = function(){
  let newArr = [];
  this.forEach(item =>{
    if(item !== 3){
      newArr.push(item)
    }
  })
  console.log(newArr)
}

arr.remove3()

다른 풀이

Array.prototype.remove3 = function(){
  for (var i = 0; i < this.length; i++) {
    if ( this[i] === 3 ) {
      this.splice(i,1);
    }
  }
};

var arr = [1,2,3,4];
arr.remove3();

console.log(arr); //[1,2,4]
  • remove(3) 하면 해당 인자 제거해주는 함수도 만들어보기

var arr = [1,2,3];

Array.prototype.remove = function(input){
  console.log(this) // [1,2,3]
  console.log(input) // 3
}

arr.remove(3)

따라서

var arr = [1,2,3];

Array.prototype.remove = function(input){
  let newArr = [];
  this.forEach((item)=>{
    if(item !== input){
      newArr.push(item);
    }
  })
  console.log(newArr)
}

arr.remove(2) //[1, 3];

만들어 쓰기 귀찮으니 find나 filter 쓰자.

ES5 신문법

prototype과 function 컨스트럭터는 구문법이다.

또한 아래 ES5 문법은 컨스트럭터가 아니다.

Object.create(프로토타입object)

// Object.create(프로토타입object)

var 부모 = {name : 'Kim', age : 50}
console.log(부모.__proto__)
//부모도 결국 var 부모 = new Object()로 생성 된 것임.

var 자식 = Object.create(부모);

자식.age = 20;
![](<https://velog.velcdn.com/images/bigwave-cho/post/6d3e3dc0-7e9b-4754-a5bf-75afe8e23c81/image.png>)

console.log(자식) //{age:20}
console.log(자식.age) // 20
console.log(자식.name)// 'Kim'
// age는 동적 변경 하였으니 20을, name은 물려받은 그대로.

var 손자 = Object.create(자식);

console.log(손자) {}
console.log(손자.name)  //'Kim'
console.log(손자.age) // 20
//손자는 name도 age도 없으니 상위 자식에서 찾고
// 그 값을 물려 받음.

ES6의 constructor

class 부모 {
  constructor(name) {
    this.name = name;
    // this.sayHi = function () {
    //   console.log('hi');
    // };
  }
  sayHi() {
    console.log('hi');
  } // constructor 바깥의 함수는 프로토타입에 저장됨.
}

var 자식 = new 부모('park');
console.log(부모.prototype);
console.log(자식.__proto__);
자식.sayHi(); //hi
console.log(자식.name) //park

https://velog.velcdn.com/images/bigwave-cho/post/f5e1512c-4fcf-4100-b570-ac7943200a23/image.png

Object.getPrototypeOf(오브젝트명)

__proto__ 보다 더 정확함.

console.log(자식.__proto__);
console.log(Object.getPrototypeOf(자식))

상속의 상속?!

class 할아버지{
  constructor(name){
    this.= 'Kim';
    this.이름 = name;
  }
    sayHi(){
    console.log('안녕')
  }
}

var 할아버지1 = new 할아버지('만수');

console.log(할아버지1.이름) //만수

class 아버지 extends 할아버지 {
  constructor(name){
    super(name);
    //부모 컨스트럭터 그대로 사용함 = super()
    //extends 쓸때는 constructor 내에서 this쓰려면 super()써줘야 에러 안뜬다.
   	// 할아버지의 construct를 가져와서 
    // 아버지 constructor 내부에 뿌리는 느낌.
    // 그래서 아버지의 프로퍼티에는 결국 this.성, this.이름에
    // 아래의 나이까지 추가되는듯
    this.나이 = 50;
  }
  sayHi(){
    console.log("꺼져!")
  }

}
var 아버지1 = new 아버지('민식');
//인자 없으면 name undefined 뜬다.

console.log(할아버지1)//{성: "Kim", 이름: "만수"}
console.log(아버지1.) // 'Kim'
console.log(아버지1.이름) // '민식'
console.log(아버지1.나이) // 50

부모 컨스트럭터에 예) name2 같은 파라미터 추가되면 똑같이 늘려주면 됨.

super()의 다른 용도.
위 코드를 살펴보면 할아버지에도 sayHi()라는 함수가 프로토타입에 저장되어있고 아버지에도 다른 콘솔로그를 출력하는 동일 명의 함수가 저장되어 있다.

당연히 JS는 프로토타입 체이닝으로 처음 sayHi가 발견된 아버지의 "꺼져"를 출력한다.

하지만!

아버지 sayHi()내에 super.sayHi()를 추가해준다면?

super은 부모 프로토타입을 의미하고 거기 있는 sayHi()를 가져온다.

따라서 "안녕" "꺼져" 두 가지 다 출력 된다

getter, setter

객체에 age가 있고 age의 나이를 + 1 하고싶다.

var 사람 = {
  name: 'Park',
  age : 30,
  nextAge(){
    console.log(this.age)
    return this.age+1;
  }
}

근데 그냥 사람.age + 1 하면 간단하지 않을까?

함수로 object 데이터를 다루는 이유?

  1. 오브젝트 자료가 복잡할 때 좋다.
    복잡하면
사람.age[0] + 1~~ 뭐 이런식으로 해야 함.
  1. 데이터 수정 시 좋다.(실수를 줄여 준다)
사람.age = 20; //이렇게 수정해야하는데 실수해서

사랑.age = "20"; //이렇게 해버리면
//age는 숫자 데이터가 아닌 스트링을 가지게 되버림.

아래의 코드처럼 만들어두면
데이터 입력시 미리 검사할 수 있기 때문에
실수 줄이기 좋다.

var 사람 = {
  name: 'Park',
  age : 30,

  setAge(num){
    if(!isNaN(num)){
    this.age = num;
    }else{
      console.log("Type only number")
    }
  }
}

get(getter), set(setter)

get, set을 함수 앞에 달아주면 사용할 때 소괄호 없어도 됨.
이것들 사용은 필수가 아니고 옵션임.

var 사람 = {
  name: 'Park',
  age : 30,

  get nextAge(){
    return this.age + 1;
  },

  set setAge(num){
    if(!isNaN(num)){
    this.age = num;
    }else{
      console.log("Type only number")
    }
  }
}
console.log(사람.nextAge); //31

사람.setAge = 20;
console.log(사람.age) //20
  • get:
    • 데이터를 꺼내 쓰는 함수
    • return 이 꼭 있어야 함.
    • 파라미터 입력 X
  • set:
    • 데이터를 변경하는 함수

    • 파라미터가 1개만 있어야 함.

      class에서 get,set 사용하기

      똑같음.

class 사람 {
  constructor(){
    this.name = 'Park';
    this.age = 20;
  }
  get nextAge(){
    return this.age+1;
  }
  set setAge(num){
    this.age = num;
  }

}

var 사람1 = new 사람();

console.log(사람1.nextAge) //21

사람1.setAge = 30;
console.log(사람1.age) //30

마무리 연습

// 1~3
class 강아지 {
  constructor(type, color){
    this.type = type;
    this.color = color;
  }
  /*한살먹기(){
    console.log('Error')
    개 고양이 둘 다 안써도 이렇게 하면 됨.*/
  if(this instanceof Cat){
    this.age++;
  }
}

var 강아지1 = new 강아지('말티즈', 'white');
var 강아지2 = new 강아지('진돗개', 'brown');

class 고양이 extends 강아지{
  constructor(type, color, age){
    super(type, color);
    this.age = age;
  }
  /*한살먹기(){
    this.age = this.age+1
  }*/
}

var 고양이1 = new 고양이('코숏','white',5);

console.log(고양이1)

강아지1.한살먹기()

고양이1.한살먹기()
console.log(고양이1.age)

// 4
class Unit{
  constructor(){
    this.공격력 = 5;
    this.체력 = 100;
  }
  get battlePoint(){
    return this.공격력 + this.체력;
  }
  set heal(num){
    this.체력 += num;
  }
}

var 유닛1 = new Unit();

console.log(유닛1.battlePoint)

유닛1.heal = 50
console.log(유닛1.체력) //150

// 5
var data = {
  odd : [],
  even : [],
  set홀짝(...rest){
    rest.forEach((item)=>{ if(item % 2 === 0){
      this.even.push(item);
    }else{
      this.odd.push(item);
    }})

  },
  sortArr(){
    console.log(this.odd.sort((a,b)=>{
  return a-b;
}));
    console.log(this.even.sort((a,b)=>{
  return a-b;
  // 오른차순 정렬 방법. sort는 문자열 정렬이라 숫자는 세팅 필요
}));
  }
}

data.set홀짝(1,3,2,5,4)
console.log(data.odd)
console.log(data.even)

data.sortArr()
profile
주먹구구식은 버리고 Why & How를 고민하며 프로그래밍 하는 개발자가 되자!

0개의 댓글