PracticeEveryday

JavaScript 본문

JavaScript

JavaScript

kimddakki 2022. 5. 4. 12:07
생성자 함수와 Ptototype 객체

● Prototype 객체

원시 타입 : Number, String, Boolean, undefined, null

객체 (Object) : 원시 타입을 제외한 모두 (Array, Function, RegExp, Date...)

 

원시 타입을 제외한 모든 객체는 모두 proto 속성을 가지고 있습니다.

그리고 객체 내부의 proto 속성은 객체가 만들어지기 위한 prototype 객체를 내부적으로 참조합니다.

그리고 prototype 객체를 참조하는 객체는 해당 prototype 객체가 가지고 있는 메소드를 상속 받습니다.

 

let arr = [1, 2, 3, 4, 5];

let sliceArr = arr.slice(0, 3);
console.log(sliceArr); // [1, 2, 3]

console.log(arr.__proto__); // Object(0) []
console.log(arr.__proto__.slice); // [Function: slice]

우리는 지금까지 " slice는 배열의 기본 메소드이니까. 배열에는 원래 저게 있어"라고 자연스럽게 생각했다.

그럼 이 slice 라는 우리가 정의한 적 없는 메소드가 어디서 튀어나와 우리가 사용할 수 있는 것일까요?

이를 자바스크립트 내부에서 이해해 보도록 하겠습니다.

 

배열 arr을 선언하고 값을 할당하는 순간 자바스크립트 내부에서는 'arr'을 만들어야 할 객체로 인식

  배열이지만 원시타입을 제외한 모든 것은 객체이기에 배열은 객체형 데이터이다.

arr 배열을 만들기 시작 할때 Array.prototype 객체의 constructor를 참조해서 만듭니다.

arr 객체 내부에는 내부적으로 proto 속성을 가지고 있습니다. 이 내부 proto 속성이 Array.prototype을 숨은 링크로 참조하게 됩니다.

=> 어떤 객체가 prototype 객체를 proto속성으로 참조 한다는 것은 해당 prototype 객체가 가지고 있는 메소드를 상속받는 다는 것!!

그러므로 Array.prototype이 가지고 있던 slice 메소드를 배열(객체타입) arr가 상속받아 사용할 수 있는 것입니다!

1. arr 배열이 선언되고, arr에 배열이 할당 됨.
2. arr에는 배열이 할당 되었으니 arr 객체를 만들자!
3. arr 객체는 어떻게 참조해야 될까
4. arr이 배열이니 Array.prototype을 참조해서 생상하자
4. arr 객체 생성
5. arr 객체는 Array.prototype객체의 컨스트럭터를 참조해서 만들었으므로 
   prototype link(__proto__)는 Array.prototype 객체를 참조 (arr 객체를 만들어낸 원형)
6. arr 객체는 Array.prototype 메소드를 상속 받는다.
7. 따라서 arr 객체는 Array.prototype slice 메소드를 사용할 수 있다.


   prototype 객체는, 간단히 말하면 어떤 객체를 만들고, 
   메소드를 상속해 주는 역할을 수행하는 내부적으로 존재하는 객체입니다.

● 생성자 함수 (Constructor function)

함수가 정의되면 내부적으로 그 함수는 자신의 prototype 객체가 생성된다.

이 함수의 정의로 생성된 prototype 객체는

prototype link와 constructor를 가지고 있는데

1. prototype link => 자신을 만들어낸 객체의 원형을 참조하는 역할

2. constructor => 정의된 함수가 생성자가 될 수 있는 자격을 부여한다.

즉 JavaScript에서 사용하는 모든 함수는 생성자의 자격을 갖추고 있다.

 

생성자

 - 생성자는 new라는 키워드와 함께 함수를 실행 했을 때 해당 함수에 정의된 내용으로 객체를 만드는 것을 의미한다.

    -> 함수 prototype 객체 내에 constructor가 있기에 가능하다.

이렇게 만들어진 객체는 함수를 정의한 것이 아니기 때문에 자신 고유의 prototype 객체를 가지고 있지 않지만

이를 통해 생성된 객체 내부에 prototype link를 가지고 있고 이 link를 통해 자신으 생성한 함수의 prototype 객체를 숨은 링크로 참조하게 됩니다.

 

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

let me = new person("kim");
console.log(me.prototype); // me는 만들어진 객체이므로 prototype 객체 없음 undefined
console.log(me.__proto__); // {}
console.log(me.__proto__.__proto__); //[Object : null prototype] {}
console.log(me.__proto__.__proto__.valueOf); // [Function: valueOf]

console.log(me.__proto__.__proto__.__proto__); // null
//console.log(me.__proto__.__proto__.__proto__.__proto__); error

// prototype에 메소드 정의
person.prototype.getName = function () {
  return console.log(this.name);
};

console.log(me); // person // {name:'kim'}
console.log(me.__proto__); // {getName: [Function (anonymous)]}
me.getName(); // kim

/**
 *
 * 1. person 함수 정의
 * 2. person 함수를 정의하는 동시에 JS는 내부적으로 person의 프로토타입 객체 생성
 * 3. person prototype 객체에는
 *    constructor => 객체 생성 가능 와 prototype link가 존재
 * 4. person.prototype 객체에 getName 메소드 정의
 * 5. person1 변수 생성 person 함수의 생성자로 객체 생성 후 할당
 * 6. person1 객체 생성
 * 7. person1 객체는 함수를 정의한 것이 아니기에 prototype 객체는 존재하지
 *    않고 person1 객체의 prototype link가 person 함수의 prototype 객체를
 *    링크로 참조할 뿐이다
 * 8. person1 객체는 person 함수의 프로토타입 객체의 constructor 로 만들어 
 *    졌으므로 (메소드를 상속) person 함수의 메소드를 사용할 수 있는 것입니다.
 */

함수를 선언하면 프로토타입 객체가 자동으로 생성된다.
프로토타입 객체에 메서드를 정의하는 방법
● 함수명.prototype.메소드명 = 메소드 내용

어떤 함수의 프로토타입링크를 해당 함수의 프로토타입이 아닌 다른 객체로 변경하기 위해선
● 함수명.prototype = 참조대상
 => 상속 가능!
위에서 객체가 prototype link 를 통해 어떤 프로토타입 객체를 참조하면 해당 프로토타입 객체의 메소드를 상속한다고 했습니다.
프로토타입의 링크를 상속받고자하는 함수의 프로토타입 객체로 변경하면 메소드를 따로 정의 할 필요없이 해당 프로토타입의 메소드를 사용할 수 있는 것입니다.

1. 부모 함수를 이용해 객체를 생성하고, 자식에 해당하는 함수의 Prototype link를 부모함수를 통해 생성한 객체에 참조하는 방식

function Person(name) {
  this.name = name || "홍길동";
}

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

function Korean(name) {}
console.log(Korean.__proto__); // {} 빈 프로토타입 객체 참조(메서드 정의x)
Korean.prototype = new Person();
console.log(Korean.prototype); // Person {name: 홍길동}
console.log(Korean.__proto__); // {}

var kor1 = new Korean();
console.log(kor1.getName()); // 홍길동
console.log(kor1.prototype); // Person {name: 홍길동} // 만들어진 객체이므로 prototype은 undefined
console.log(kor1.__proto__); // Person {name: '홍길동'} 만들어진 new Person()의 객체를 참조

// Korean함수의 인자 name이 Person 함수의 name과 연결되어 있지 않아 홍길동이 찍힘
var kor2 = new Korean("류현진");
console.log(kor2.getName()); // 홍길동

/**
 * 1. Person 함수 정의 + 동시에 Person 함수의 프로토타입 객체 생성
 * 2. Person 함수의 프로토타입 객체에 getName 메소드 정의
 * 3. Korean 함수 정의 + 동시에 Korean 함수의 프로토타입 객체 생성
 * 4. Korean 함수의 프로토타입 링크는 기본적으로 자신의 프로토타입 객체를
 *    참조함.
 * 5. Korean 함수의 prototype link를 Person 함수를 생성자로 호출한 객체로
 *    변경
 *    Korean 함수의 prototype link = Korean 함수의 프로토타입 객체 ->
 *    Person 함수 생성자 호출로 생성된 객체
 * 6. Person 함수의 프로토타입 객체의 getName 메소드를 사용할 수 있습니다
 */

 

 


2. 부모함수의 this에 자식 객체를 바인딩 하는 방법

function Person(name) {
  this.name = name || "홍길동";
  this.age = 30;
}
Person.prototype.getName = function () {
  return this.name;
};

function Korean(name) {
  console.log(this); // Korean {}
  Person.apply(this, arguments); // this에 자식 객체를 바인딩!!
  console.log(arguments); // arguments = {"0" : 류현진, "1" : 10}
  console.log(this); // Korean{name : "류현진", age : 30}
}

var kor1 = new Korean("류현진", 10);
console.log(kor1.name); // 류현진
console.log(kor1.__proto__); // {} 빈 객체
console.log(kor1.__proto__.__proto__); // [object: null prototype] {}
console.log(kor1.prototype); //undefined
// console.log(kor1.getName()); // TypeError

/**
 * 1. Korean 함수를 정의 할 때, 함수의 내용으로 Person함수를 apply를 통해 간접
 *    실행함.
 * 2. 간접 실행(call, apply 메소드)는 첫 번째 인자에 실행 문맥을 지정해 주는
 *    것임
 *    => 두번째 인자에는 유사 배열만 들어올 수 있습니다. (arguments 객체는
 *    유사 배열)
 * 3. Korean 함수를 실행 할 때 Person 함수를 this로 바인당 해 간접 실행하기
 *    때문에 Korean 함수는 Person 함수와 똑같이 작동합니다
 *    => Korean이 실행되면 Korean 안에서 Person이 실행됨
 * 4. 따라서 생성된 kor1 객체에서 name 속성 사용 가능
 *    (Person의 this가 Korean의 this와 같아짐)
 *    하지만 Korean 함수와 Person.prototype 객체간의 연결 고리가 없기 때문에
 *    getName method는 사용 불가능 함
 */

3. 생성자는 빌려쓰고 프로토타입을 지정해 주는 방법

function Person(name) {
  this.name = name || "홍길동";
}

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

function Korean(name) {
  Person.apply(this, arguments);
}
Korean.prototype = new Person();

var kor1 = new Korean("류현진");
console.log(kor1.getName()); // 류현진

/**
 * Person 함수를 간접 실행하여 this 바인딩 (name 사용가능)
 * Korean 함수의 prototype 링크를 Person 생성자를 통해 객체에 참조
 * => name 속성은 this 바인딩을 통해 Korean 생성자가 Person 함수내용을
 *    동작시켜 사용가능하고,
 * => getName 메소드는 Korean 함수의 프로토타입 링크를 new Person() 객체에
 *    참조함으로 사용가능 한 것입니다.
 *
 * 하지만 단점으로는 kor1의 객체에 name 속성이 존재하고
 * 프로토 타입 링크를 걸어주기 위해 만든 new Person 객체에도 name 이 존재한다.
 * 메모리 낭비?
 */

4. 프로토타입 공유하기

function Person(name) {
  this.name = name || "홍길동";
}

Person.prototype.getName = function () {
  return this.name;
};
console.log(Person.prototype); // {getName : [Function (anynymous)]}

function Korean(name) {
  this.name = name;
}
console.log(Korean.prototype); // {}
Korean.prototype = Person.prototype;
console.log(Korean.prototype); // {getName : [Function (anynymous)]}

var kor1 = new Korean("류현진");
console.log(kor1.getName()); // 류현진

/**
 * Korean 함수를 정의 할 때 Korean 생성자로 만들어질 객체에 name 속성 따로 부여
 * Korean 함수의 prototype링크를 Person 함수의 prototype 객체로 지정
 * 세 번째 방법과 달리 중간에 Person 함수로 생성한 객체가 존재하지 않습니다.
 */
첫 번째 부터 네 번째 까지의 방식을 classical 방식이라고 하는데,
이는 JAVA에서의 객체를 생성하는 방법과 유사하기 때문입니다.

5. Object.create 메소드를 활용한 prototypal 방식!

 

// Object.create(새로만든 객체에 지정될 프로토타입 객체, 새롭게 생성될 객체에만 부여할 속성) return 값은 새로운 객체

let person = {
  type: "사람",
  getType: function () {
    return this.type;
  },
  getName: function () {
    return this.name;
  },
};

let jin = Object.create(person, { job: { value: "야구선수" } });
console.log(jin.prototype); // undefined 만들어진 객체는 prototype이 없다!
console.log(jin.__proto__);
// {  type: '사람',  getType: [Function: getType],  getName: [Function: getName]}

jin.name = "류현진";

console.log(jin.getType()); // 사람
console.log(jin.getName()); // 류현진
console.log(jin.job); // 야구선수
console.log(person.job); // undefined

/**
 * person 객체를 정의 => type, getType, getName 속성, 메서드가 존재
 * Object.create(생성하는 객체의 프로토타입 객체를 person 객체로 지정)
 * => person 객체를 상속 받음.
 * 2번째 인자에 jin 객체만의 속성을 부여 <= person에서 접근 불가!!
 */

함수를 정의하면 자동으로 그 함수에 대응되는 Prototype 객체가 생성된다.
Prototype 객체는 Constructor와 Prototype Link를 가지고 있는데
Constructor는 정의된 함수가 생성자가 될 수 있는 자격을 부여하고

Prototype Link는 자신을 만들어낸 객체의 원형을 참조하는 역할을 합니다.

 


 

 

생성자 함수와 prototype 객체

안녕하세요, 프로독학러 입니다. 이번 포스팅에서는 자바스크립트의 생성자 함수와 prototype 객체에 대해서 알아보도록 하겠습니다. 먼저 prototype 객체에 대해서 알아보겠습니다. 일단 이름부터

pro-self-studier.tistory.com

 

'JavaScript' 카테고리의 다른 글

JavaScript  (0) 2022.05.07
JavaScript  (0) 2022.05.07
JavaScript  (0) 2022.05.04
JavaScript  (0) 2022.05.03
JavaScript  (0) 2022.05.02
Comments