본문 바로가기

JS

JS #8-자바스크립트 기초 개념8 (가비지컬렉터, this와 메서드, 클래스)

가비지컬렉터

참조 한개일 때

객체의 값을 다른 값으로 덮어주면 참조가 사라지면서 가비지컬렉터가 본래 값을 수집한다. 

 

 

참조 두개일 때 

 

참조 한개의 값이 덮어써지더라도, 한개는 여전히 가르키고 있으므로 가비지컬렉터는 수집해가지 않는다. 

 

 

도달할 객체가 없을 때

 

 

서로 엮여있는 가족 객체가 있다. 

John 객체에 연결된 참조를 삭제해보자

 

 

John 객체를 가르키고 있던 father과 husband 참조가 삭제되므로써 

이제 John에 들어오는 객체는 없다. John에서 나가는 참조는 있지만, 

이는 도달 불가능한 상황이므로 가비지 컬렉터에 의해 John이 수집된다. 

 

 

근원(root) 객체에 null이 온다면

 

근원 (root) 자체가 null로 덮어써진다면, 그 아래에

가르키고 있던 모든 객체들은 도달할 수 없는 상태가 되므로 가비지 컬렉터에 의해 수집된다. 

 

루트에서 도달하지 못하는 객체들은 모두 가비지 컬렉터가 수집하게 된다. 

 

 

 


 

this와 메서드 

 

 

메서드

user 객체에 function 메서드를 정의 ( 객체 안에 함수는 메서드라고 정의한다) 

 

 

normal과 concise method 의 정의

normal method -> constructor 내장

concise method - > constructor 없음

 

 

 

this

uer.sayHi() 가 실행되는 동안 this는 user를 나타낸다. (객체를 호출한 대상을 가르킴)

this를 사용하지 않고 외부 변수(user.sayHi) 를 참조해 객체에 접근하는 것도 가능하다. 

다만, user가 다른 값으로 덮어써졌을때를 생각해보면 

 

이 때에는 user를 참조하여 직접 외부변수에 접근한 admin().sayHi가 문제를 일으킨다. 

 

 

자유로운 this

this는 '점 앞의 객체'가 무엇인지에 따라 자유롭게 결정된다.

 

 

 

화살표함수 (this와의 바인딩 없다. )

 

 

 

 

실습1. 객체 내부의 menu 객체의 배열 요소 더하기

<1번째 방법>

 
const shopOrder = {
    date: '2023. 12. 06',
    tableIndex: 5,
    menu: [
      { name: '통 새우 돈까스', price: 13000, count: 2 },
      { name: '치즈 돈까스', price: 10000, count: 1 },
    ],
  };
 
//forEach를 사용해서 모든 메뉴의 총 금액

let total = 0;
shopOrder.menu.forEach((menu) => {
    total += menu.price * menu.count;
    return total;
});

console.log(total);

 

forEach는 배열을 탐색하는 것이므로 먼저 배열에 접근해줘야 한다. => shopOrder.menu 까지 접근

shoOrder.menu.forEach() 를 돌려주면 

이렇게 각각의 배열 하나하나를 탐색하게 될 것이다. (인자 명 : menu)

이의 price과 count에 접근이 필요하므로 

menu.price / menu.count 를 통해 연산을 하고 출력한다. 

 

 

<2번째 방법>

const result = shopOrder.menu.reduce((acc,cur) => {
    return acc += (cur.price * cur.count);
},0)
console.log(result);

reduce를 이용해서 값을 누적해준다. 

 

 

<객체의 프로퍼티로 메서드 정의> 

const shopOrder = {
    total: 0, //최종 값을 담을 total
    date: '2023. 12. 06',
    tableIndex: 5,
    menu: [
      { name: '통 새우 돈까스', price: 13000, count: 2 },
      { name: '치즈 돈까스', price: 10000, count: 1 },
    ],
    totalPrice() {
        this.total = this.menu.reduce((acc, cur) => {
            return acc + (cur.price * cur.count);
        }, 0)
        return this.total; // 계산된 값을 this의 total 프로퍼티로 내보냄
    }
  };
 
  shopOrder.totalPrice();
  console.log(shopOrder.total);

객체의 프로퍼티로 해당 함수를 정의했다. 

결과값은 this.total값에 저장해준다. 이를 위해서 객체에도 total:0 으로 초기값을 선언해준다. 

 

 

예제2. 네비게이션

<오류상황>

  const navigationMenu = {
    name: '글로벌 내비게이션',
    items: [
      { id: 'link-g', text: 'Google', link: 'https://google.com' },
      { id: 'link-n', text: 'Naver', link: 'https://naver.com' },
    ],
    getItem(index) { //concise 메소드의 this는 호출을 한 대상
      return this.items[index];
    },
    addItem: (newItem) => { //arrow function의 메소드는 this를 가지지 않기 떄문에 window
      this.items.push(newItem);
    },
  };

  navigationMenu.addIem ({
    id: 'link-l',
    text: 'lycos',
    link: 'http://lycos.co.kr'
  })

ㄴ  additem 은 this를 가지지 않기 때문에 새로운 객체가 전달되지 않는다. 

따라서 arrowfunction을 normal function으로 변경한다. 

 

  const navigationMenu = {
    name: '글로벌 내비게이션',
    items: [
      { id: 'link-g', text: 'Google', link: 'https://google.com' },
      { id: 'link-n', text: 'Naver', link: 'https://naver.com' },
    ],
    getItem(index) { //concise 메소드의 this는 호출을 한 대상
      return this.items[index];
    },
    addItem: function(newItem) { //arrow function의 메소드는 this를 가지지 않기 떄문에 window
      this.items.push(newItem);
    },
  };

  // 전달될 프로퍼티들의 이름이 변경되지 않도록 typeScript로 강제한다.
  navigationMenu.addIem ({
    id: 'link-l',
    text: 'lycos',
    link: 'http://lycos.co.kr'
  })
 

 


 

프로토타입 상속

프로토타입 : 부모의 능력

자바스크립트의 모든 객체는 [[prototype]] 이라는 숨겨진 객체를 갖는다. 이는 NULL 또는 객체를 가르키고있다. 

어떠한 객체를 참조하는 경우를 '프로토타입' 이라고 부른다.

 

프로토타입 동작방식 

obect에서 프로퍼티를 읽으려고 하는데 없으면,

자동으로 프로토타입(부모의 능력) 에서 해당 프로퍼티를 찾는다. 

 

 

프로토타입 강제 주입

상속 방법 1

rabbit에게 강제로 rabbit.__proto__ = animal 을 준다.  (던더프로토)

이렇게 되면 rabbit도 부모의 프로퍼티인 eats를 갖는다. 

 

상속방법 2

객체 내부에 직접 던프로토를 작성해줌으로써 상속을 시킬 수 있다. 

 

 

 

상속구조

하단으로 계속해서 상속이 이어지는 이런 구조도 가능하다. 

 

 

프로토타입은 '읽기전용' 이다.

프로토타입은 프로퍼티를 읽을 때만 사용한다. 

수정하거나 지우는 연산은 객체에 직접 해야한다. (자식 객체에) 

 

상속과 프로퍼티

부모의 메서드를 정의할 때 this를 사용했다면, -> 나의 메서드를 호출한 대상이 되는 것인데 

부모가 가지고 있는 메서드라고 하더라도

자식의 rabbit이 __proto__를 상속받아, 해당 메서드(sleep) 을 사용한다고 했을 때

this.isSleeping = true; 의 this는 rabbit의 프로퍼티가 되어 사용될 수 있는 것이다. 

 

 

  • 자바스크립트에 모든 객체에는 숨긴 프로퍼티 [[prototype]] 이 있다.
  • obj.__proto__ 를 사용하여 접근할 수 있다.
  • 프로토타입에서 상속받은 method라도 obj.method()를 호출하면, this는 호출 대상인 객체를 가르키게 된다. 
  • for...in 을 사용하려면 hasOwnProperty를 call 해서 사용하는 방법이 보다 정확할 수 있다. 

 

 

실습
//객체와 프로퍼티, 메서드 정의
const animal = {
    legs: 4,
    tail: true,
    stomach: [],
    getEat() {
        return this.stomach
    },
    setEat(food){
        this.prey = food;
        this.stomach.push(food);
    }
}

animal.setEat('과일');

setEat을 통해 과일을 넣으면

콘솔에 animal을 열거하면 grey가 생성된 것을 볼 수 있다.

 

 

 

 

getter와 setter을 정의하기
const animal = {
    legs: 4,
    tail: true,
    stomach: [],
    get eat() {
        return this.stomach
    },
    set eat(food){
        this.prey = food;
        this.stomach.push(food);
    }
}

animal.setEat('과일');

 

<getter와 setter 사용>

getter
setter

 

 

프로토타입 상속 방법 2가지
const animal = {
    legs: 4,
    tail: true,
    stomach: [],
    get eat() {
        return this.stomach
    },
    set eat(food){
        this.prey = food;
        this.stomach.push(food);
    }
};


const tiger = {
    pattern: '호랑이무늬',
    hunt(target) {
        this.prey = target;
        return `${target}에게 조용히 접근합니다.`
    }
};

tiger.__proto__ = animal; //tiger는 animal로 부터 상속을 받는다.

tiger에게 animal을 상속시켜준다. 

 

 

const animal = {
    legs: 4,
    tail: true,
    stomach: [],
    get eat() {
        return this.stomach
    },
    set eat(food){
        this.prey = food;
        this.stomach.push(food);
    }
};


const tiger = {
    pattern: '호랑이무늬',
    hunt(target) {
        this.prey = target;
        return `${target}에게 조용히 접근합니다.`
    }

    __proto__ = animal
};

tiger 객체 내부에서 상속을 주는 것도 가능하다. 

 

 

다중 상속

 

const animal = {
    legs: 4,
    tail: true,
    stomach: [],
    get eat() {
        return this.stomach
    },
    set eat(food){
        this.prey = food;
        this.stomach.push(food);
    }
};


const tiger = {
    pattern: '호랑이무늬',
    hunt(target) {
        this.prey = target;
        return `${target}에게 조용히 접근합니다.`
    }

    __proto__ = animal
};

tiger.__proto__ = animal; //tiger는 animal로 부터 상속을 받는다.



const 백두산 호랑이 = {
    color: 'white',
    nsmr: '백랑이'
    __proto__ = tiger

}

맨 아래에 있는 백두산 호랑이 객체도 상위에 있는 메소드를 사용할 수 있다. 

 

백두산 호랑이의 프로토타입들

 

 

 


함수의 prototype 프로퍼티 
*함수를 객체로 만들면 객체가 된다! 

 

function F() {

}

const a = new F();

 

생성자 함수를 통해 만들어진 객체 a = '인스턴스' 

생성자함수를 통해 만들어진 모든 인스턴스에는 

던던프로토로 상속이 만들어진다.  

 

 

 

생성자 함수를 만들고 (어차피 객체를 생성하는 기능을 하는 것.) 

Rabbit.prototype = animal 이라는 프로퍼티로 상속을 설정해주고

ㄴ 이 때 기본으로 세팅되는 .prototype은 [[Prototype]] 과는 다르다.  .prototype은 new Rabbit()을 호출할 때

만들어지는 새로운 객체의 [[Prototype]]을 설정하는 프로퍼티이다.

[[Prototype]]은 부모 객체인 animal이 되겠지 ~~???

ㄴ .prototype의 값은 null / 객체 만 가능하다. 

ㄴ 일반 객체엔 .prototype을 추가해도 아무 반응이 없다! 오로지 '생성자 함수' 에만 생성됨!

 

 

 

객체를 공장처럼 찍어내는 것이다. -> new Rabbit('흰 토끼')

 

새로운 객체를 만든 후에 F.prototype을 바꾸게 되면?

기존에 만들어놓은 객체 : 기존의 상속을 따르게 된다. 

이후에 만들어놓은 객체 : 새로운 상속을 따르게 된다. 

 

 

 

 

생성자 함수를 만들면, 안에 내장하는 constructor가 있고

내장 constructor는 또 rabbit() 생성자함수를 가르키고 있다. 

ㄴr의 constructor는 rabbit() 함수를 가르키고 있다.

ㄴ이 기능을 통해 다시 인스턴스를 만들어낼 수 있는 것이다. (이런 행동을 많이 하지는 않는다)

인스턴스를 만들어내기

 

 

 

실습

function Rabbit(){}
Rabbit.prototype = {
  eats: true
}

let rabbit = new Rabbit; // 객체가 생긴 후에


Rabbit.prototype = {}; // 프로토타입이 변경되었으므로


alert(rabbit.eats); //이미 생성되어있던 rabbit의 프로토타입에는 변화가 없다.

 

function Animal() {
    this.legs = 4;
    return '메롱'; // 생성자 함수는 리턴 갑 작동 X
}

 

function Animal(){
    this.legs = 4;
    this.tail = true;
    this.stomach = [];
 
    this.getEat = function (){
      return this.stomach
    }
   
    this.setEat = function (food){
      this.prey = food;
      this.stomach.push(food)
    }
 
  }
 
  const a = new Animal();

 

 

  function Tiger() {
    this.name = name;
    this.pattern = '호랑이무늬';
    this.hunt = function(target) {
        this.prey = target;
        return `${target}에게 천천히 접근한다.`;
    }
  }

  const 한라산호랑이 = new Tiger('한돌이');

 

ㄴ 현재 Tiger은 상단에 있는 Animal 생성자 함수와 전혀 관련이 없다. 이를 연결시켜 주려면? 

 

방법1 ) Animal로 생성된 인스턴스 객체를 프로토타입과 연결시켜준다. 

  Tiger.prototype = a

 

방법2 ) Animal을 빌려온다 (상속이라고 보기는 좀 어려움)

 

call -> 함수의 메서드 즉, 함수가 쓸 수 있는 능력이다.  대표적으로 (call, apply, bind)

call의 능력 : this를 대신 전달해준다. 

 

함수의 실행 결가로 age: 11 이 출력된다.

마치 this는 age:11로 설정 된 것 처럼 설정해준다. 

따라서


  function Tiger() {
    Animall.call(this);
    this.name = name;
    this.pattern = '호랑이무늬';
    this.hunt = function(target) {
        this.prey = target;
        return `${target}에게 천천히 접근한다.`;
    }
  }

//   Tiger.prototype = a

  const 한라산호랑이 = new Tiger('한돌이');

Animall 함수를 call이 실행하면서 this(한라산 호랑이) 인스턴스가 전달되는 것이다. 

즉, 내가 생성한 인스턴스를 전달하는 것과 같다.  = Animal 함수의 this 는 모두 '한라산호랑이' 가 된다. 

한라산 호랑이객체가 모든 프로퍼티를 갖게되는 것이다! 

 

+추가

call

 

call로 함수를 부를 때 매개변수는 이렇게 처리한다. 

문자열로 넘어가는 첫 번째 매개변수 'a' 는 this를 뜻한다. 

apply

apply로 함수를 부를 때 call과 거의 동일하나, 

매개변수 전달 방식이 '배열' 임이 다르다! 

 

bind

 

결과

bind는 함수를 묶고, 바로 실행하지는 않는다. (call과apply는 바로 실행)

따라서, 별도로 실행을 시켜야 한다. => aa() 로 실행어 입력해야함

매개변수는 call과 같이 콤마로 전달을 해준다. 

 

 

 

생성자 함수의 종류 2가지

 

prototype이 붙은 것 -> 상속된 애들만 쓸 수 있다. 

static Method -> 누구나 다 쓸 수 있다. 

 

 

 

<정리> 

F 라는 생성자 함수가 있을 때, 이는 F.prototype을 통해서 

만들고자하는 obj = new F()에 따라 생성된 obj의 __proto__를 설정할 수 있다. 

따라서  F.prototype을 수정하면 영향을 받는 obj의 __proto__가 일괄적으로 수정된다. 

다만, F.prototype 의 참조값이 변경되는 경우 (메모리상의 참조값)

변경되기 이전에 생성된 객체와 변경된 이후 생성된 객체의 prototype은 다르다. 

 


클래스

클래스는 다음과 같은 기본 문법을 사용하여 만들 수 있다. 

이렇게 클래스를 만든 뒤, 

new MyClass() 를 호출하면 내부에서 정의한 메서드가 들어있는 객체가 생성된다! 

(constructor는 자동으로 호출됨, 특별한 절차 없이 객체를 초기화할 수 있다. )

 

클래스의 형태
*메서드 사이엔 쉼표를 넣지 말자!
class Animal {

}

const a = new Animal();
class User {
  constructor(name) { //자동으로 실행되어 , 매개변수도 자동으로 넘어옴
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }
}

let user = new User("John");
user.sayHi;

 

 

클래스의 특징
1. 클래스에 정의된 메서드는 열거할 수 없고, 프로퍼티에 추가된 메서드의 enumverable 플래그는false
2. 클래스는 항상 엄격모드로 실행된다. 클래스 생ㅅㅇ자 안 코드 전체엔 자동으로 엄격모드가 적용된다. 

 

 

정적메서드

 

정적 메서드는 메서드를 프로퍼티 형태로 직접 할당하는 것과 동일하다. 

User.staticMethod() 가 호출될 때, this의 값은 User그 자체 (점 앞 객체) 

1. 정적 메서드는 어떤 특정한 객체가 아닌, 클래스에 속한 함수를 구현하고자 할 때 주로 사용됨

2. new "클래스이름" 으로  인스턴스 객체를 생성하지 않아도 해당 메소드를 사용할 수 있다는 장점이 있다.

냅다 클래스 이름으로 정적 메소드 사용해버리기!

 

 

 

<실습>

class Animal {
 
  constructor(name){
    this.name = name;
    this.stomach = [];
    this.legs = 4;
    this.tail = true;
  }

  get eat(){
    return this.stomach
  }

  set eat(food){
    this.prey = food;
    this.stomach.push(food);
  }

}


class Tiger extends Animal{
   
  constructor(name){
    super(name)
    this.pattern = '호랑이무늬'
  }

  static options = {
    version: '0.0.1',
    company: 'like-lion',
    ceo: '---'
  }

  static bark(){
    return '어흥!'
  }

  hunt(target){
    return `${target}에게 조심히 접근한다.`
  }
}



const tiger = new Tiger('한돌이');
  • getter, setter메서드는 User.prototype 에 쓰인다.
    때문에 사실상 class Animal에 쓰인 get eat & set eat 은  User의 부모인 Object의 객체처럼 쓰이므로,
    접근할 때 tiger.eat 으로 쓴다. 
    ㄴ tiger.eat / tiger.eat = '딸기' 
  • static 메서드는 클래스에 직.접 접근해야 사용이 가능하다.
    Tiger.bark() 
    이유는 static 메서드는 생성자 함수이기 때문에 인스턴스에서 직접 접근이 불가하다.