JavaScript

Prototype Chain과 Pseudoclassical Instantiation 그리고 class- JavaScript

파란배개 2020. 10. 28. 17:32
prototype 모델의 청사진을 만들 때 쓰는 원형 객체이다(original form).
같은 생성자로부터 만들어진 인스턴스 객체들은 모두 이 원형 객체를 공유한다.
constructor(생성자) 인스턴스가 초기화 될 때 실행하는 생성자 함수이다.
this 함수가 실행될 때, 해당 scope마다 생성되는 고유한 실행 context(execution context).
new 키워드로 생성했을 때는 해당 인스턴스가 바로 this의 값이 된다.
.call(this) 첫 번째 인자로 전달된 객체를 this로 삼는다. 뒤에 인자를 추가해 넘겨줄 수 있다. 인자를 list 형태로 넘겨준다.
.apply(this) 첫 번째 인자로 전달된 객체를 this로 삼는다. 뒤에 인자를 추가해 넘겨줄 수 있다. 인자를 Array 형태로 넘겨준다.
.__proto__(프로토 체인) 해당 인스턴스가 가진 가진 부모 프로토타입의 속성을 확인할 수 있다.

 

Pseudoclassical Instantiation으로 인스턴스 만들기

 

으로 해당 인스턴스가 가진 가진 프로토타입의 속성을 확인할 수 있다

let Human = function(name) {
  this.name = name;
}
Human.prototype.sleep = function() {
console.log('Zzz')
};
let steve = new Human('steve');
//steve.__proto__ === Human.prototype이다.
//steve.__proto__.__proto__ === Object.prototype이다.
//여기서 Object는 모든 객체들의 상위객체를 뜻한다.
//steve는 상위객체 Object의 속성들을 상속받았기 때문에 Object의 methods를 사용할 수 있다.


배열도 같은 방식으로 만들어졌기에 new operator로 새로운 배열을 만들 수 있고 methods도 사용할 수 있는 것이다

//ex)배열의 예시
let Array = function(location) {
  [native code]
}

Array.prototype.push = function() {};
Array.prototype.slice = function() {};
...
let arr = new Array();

Pseudoclassical Instantiation에서 상속 연결을 하는 방법

Object.create는 첫 번째 인자로 들어가는 프로토타입 객체를 기반으로 프로토타입을 만든다

(쉽게 생각해서 기반이 되는 프로토 타입 객체를 카피한다)

Human.call(this, name);
//this의 context를 상위 객체 Human으로 넘긴다.
Student.prototype = Object.create(Human.prototype);
//object.create()는 Student.prototype에 Human.prototype을 상속해준다.
//object.assgin()은 Deep copy로 Human.prototype과 동일한 내용을 가지지만 다른 주소값을 가진 새로운 객체를 만들어 넣어준다.
Student.prototype.constructor = Student;
//Object.create(Human.prototype)으로 Student.prototype이 Student의 constructor가 아니라
//Human의 constructor을 바라보게 되므로 이를 해결하고 상속을 정확하게 만들기 위해서는
// Student.prototype.constructor = Student;로 명시적으로 설정해줘야 한다.
Student.prototype.learn = function() {}//새로 추가할 prototype.methods를 입력한다.

 

Tip. 왜 여기서 Student.prototype = Human.prototype 대신

Student.prototype = Object.create(Human.prototype)을 쓸까?

  • Student.prototype은 Human.prototype의 메모리 주소 값을 복사할 뿐으로(Shallow copy)  자식 객체인 Student.prototype의 내용이 변경된다고 했을 때 데이터의 주소값을 공유하는 부모 객체인 Human.prototype의 내용도 변경되는 문제가 생긴다.

 

 

let Human = function(name) {
  this.name = name;
}

Human.prototype.sleep = function() {
  console.log(this.name + ' is sleeping...');
};

let steve = new Human('steve'); //새로운 인스턴스 생성

let Student = function(name) {
  Human.call(this, name); // Human.apply(this, arguments)
} 
//Human.call(this, name)이 없으면 new operator로 만들어지는 Student의 인스턴스가 
//Human의 prototype method를 사용할 수 없다. 왜냐하면 this가 Human까지 올라가지 못하기 때문이다.
//때문에 Human.call(this, name)을 사용해 this를 끌어올려줘야 한다.

Student.prototype = Object.create(Human.prototype);
Student.prototype.constructor = Student;
Student.prototype.learn = function() {};

let john = new Student('john');
john.learn(); //Student의 mothod인 prototype.learn은 잘 작동한다.
john.sleep(); //하지만 Human의 method인 prototype.sleep은 Human.call(this, name)가 없다면 
//console.log(john.sleep())은 undefined is sleeping이 출력될 것이다. 위에 설명한 대로, 
//this는 Student가 되고 Human까지 올라가지 못하기 때문이다.


이렇게 복잡한 Instance 만들기지만, Class가 등장하면 어떨까?

 

class Human {
  constructor(name) {
    this.name = name;
  }
  sleep() {//관련 prototype.method들도 class란 키워드 안쪽에 담아놓을 수 있다!
  }
}

let steve = new Human('steve');

class Student extends Human { 
//prototype을 상위객체와 연결해주고 constructor를 연결해주는 부분을 extends 하나로 대체할 수 있다!
  constructor(name) {
    super(name); //this가 가리키는 것을 상위 객체로 바꾸는 것을 super()로 대체할 수 있다!
  }
  learn() {//관련 prototype.method들도 class란 키워드 안쪽에 담아놓을 수 있다!
  }
}
let john = new Student('john');
john.learn();
john.sleep(); // john is sleeping...
  • extends Human : prototype을 상위객체와 연결해주고, 프로토타입을 연결할 때 함께 상위객체와 연결된 연결된 constructor를 다시 하위객체인 Student와 다시 연결해주는 아래 코드를 대체한다
Student.prototype = Object.create(Human.prototype);
Student.prototype.constructor = Student;
  • super(name) : this가 가리키는 context를 상위 객체로 바꾸는 Human.call(this, name)을 대체한다

Tip1. 부모와 자식의 arguments 모양이 똑같다면 자식의 constructor를 생략할 수 있다.

constructor(name);
  super(name);
}
//위 코드라면 이 부분이 생략 가능하다.

class나 Pseudoclassical Instantiation나 원리는 똑같다. 다만 class를 통해 문법적인 편의를 제공한 것일 뿐이다.

주로 사용하는건 class 코드이지만 Pseudoclassical Instantiation을 통해 원리를 이해해야 한다.


Tip2. 원본 함수 methods를 똑같이 쓰되, 다르게 사용할 수 있는 OOP의 다형성을 응용하면 기존 methods의 기본 동작을 확장하는 형태로 사용할 수 있다.

 

  • Pseudoclassical Instantiation의 경우
function Human(name) {
  this.name = name;
}

Human.prototype.sleep = function() {console.log('zzz')}

function Student(name) {
  Human.apply(this, arguments)
}

Student.prototype.sleep = function() {
  Human.prototype.sleep.apply(this);//기존 methods의 기능
  console.log('자면 안 돼!');//Student에서 추가된 새로운 methods의 기능
  //Human.prototype.sleep과 Student.prototype.sleep은 같은 context(this가 가리키는 것)을 공유한다.
}

 

  • class의 경우
class Human {
  constructor(name) {
  this.name = name;
  }
  sleep() {
    console.log('zzz');
  {
}

class Student extends Human {
//여기서 상위 객체와 arguments가 동일하므로 constructor는 생략한다.
  sleep() {
    super.sleep; //기존 methods의 기능
    console.log('자면 안 돼!'); //Students에서 추가된 새로운 methods의 기능
}