[JavaScript] JS_ 7. Prototype & Inheritance / class

jungeundelilahLEE·2020년 10월 29일
1

JS_개념 및 동작원리

목록 보기
10/11

# INDEX

1. Values & Data type
2. Operators
3. Control flow
4. Scope & Hoisting
5. Object & Array
6. This
7. Prototype & Inheritance
   7-1. Prototype
   7-2. 생성자 함수
   7-3. 인스턴스
   7-4. Prototype Chain
   7-5. Inheritance

8. Function
9. Callback function
10. Closure
11. Class
12. Others


7. Prototype & Inheritance


Prototype (프로토타입)

개념

  • Prototype이란?
    • 사전 뜻 : 원형, 견본, 전형, (후대 사물의) 선조, 원조
    • 자바스크립트는 "프로토타입 기반 객체지향 프로그래밍 언어"
    • 프로토타입 기반 객체지향 프로그래밍 언어는 : 클래스 없이도 객체를 생성 가능
    • JS의 모든 객체는 자신의 부모역할을 담당하는 객체와 연결되어 있음
    • 따라서, (객체지향의 "상속"을 구현하기 위해) 부모 객체의 프로퍼티 or 메소드를 상속받아 사용할 수 있도록 함
    • 이때의 부모객체를 "프로토타입 (객체)"이라고 함

생성자

  • 기본적으로 함수
  • 함수를 호출할 때, 호출시 new를 붙이면, 단순 함수가 아니라 "생성자"함수가 됨 ()

이런 사전적 개념이 잘 와닿지 않는 것 같아 오늘은 줄글로~

프로토타입은 자바스크리트에 있어서 매우매우매~우매우 중요한 개념이다.
생성자 함수에 대해서 먼저 짚고 넘어가보자.

생성자 함수

new operator

  • Creates a blank, plain JavaScript object;
  • Links (sets the constructor of) the newly created object to another object by setting the other object as its parent prototype;
  • Passes the newly created object from Step 1 as the this context;
  • Returns this if the function doesn't return an object.
  • "new 키워드와 함께 어떠한 함수를 실행"할 때,
    이 함수를 "생성자 함수"라고 한다.
  • 생성자 함수는 함수명을 "명사"로, 함수명 첫글자를 "대문자"로 표기한다.
  • 생성자 함수의 "기본 return(반환)값은 "this" 이며, 일반적으로 return값을 명시하지 않는다.
    -> Person 함수의 return 값이 없다. (일반 함수에서 return값이 없으면, 원래는 undefined)
    -> 그러나, 기본 return값이 this이므로, this가 빈객체를 만들고, name이라는 속성과 age라는 속성을 만들어 준 뒤 결과인 객체가 리턴된다.
    -> (*일반적으로 프로퍼티 안의 함수를 받는 프로퍼티를 메소드라 한다)
function Person (name, age) {
  // this = {}; 라고 생각해보자
  this.name = name;
  this.age = age;
  // return this; 라고 생각해보자
} 
let delilah = new Person ("jung eun", 30);
console.log(delilah); 	// Person {name: "jung eun", age: 30}
  • return값이 없음에도 불구하고, 특정 객체가 반환되어 만들어 놓은 변수에 담긴다.
  • 만약, 객체를 return하면, 해당 객체를 return한다. == 객체를 return하지 않으면, this를 return한다.

다음으로는,
자바스크립트의 객체들은 어떻게 만들어질까? 라는 질문이다.
일반적으로 아래와 같이 간단하게 만들 수 있다.

let obj = {};
let arr = [];
let func = function () {};

이 과정은 사실, (우리 눈에 보이지 않지만) 백그라운드에서는 다음과 같이 동작한다.

let obj = new Object();
let arr = new Array();
let func = new Function();

여기에서 Object, Array, Function 은 생성자함수이다.

따라서, 모든 자바스크립트 객체는 생성자 함수를 이용해서 만들어진다고 할 수 있다.
(약간의 예외적인 경우가 있음)


이제 드디어 prototpye!! 에 대해서 알아보자.
이전에, 함수는 객체이다. 따라서 속성을 가질 수 있다.
(사실 아래처럼 쓰는 경우는 거의 없다..)

function foo (a,b) {
  return a+b;
}
foo.title = "Add all parameters";
console.log(foo.title); // "Add all parameters"

모든 자바스크립트 함수는, 태어날 때 항상 always 프로토타입을 하나씩 가지고 태어난다. (1:1 매칭 / 함수 하나당, 프로토타입 하나)
예를 들어, 배열을 만들 때, 항상 length라는 속성을 가지고 태어나듯이~
그렇다면 이 함수가 만들어질 때, 항상 가지고 오는 prototype은 어떤 속성을 가질까?

자바스크립트의 모든 함수에는 prototype 속성이 있다.

그런데, new키워드 없이 함수를 호출하는 경우에 prototype은 어떤 역할을 할까? 아무.. 역할도 하지 않는다.

따라서, 각 함수가 가지고 있는 prototype 속성은, 그 함수가 "생성자 함수로 실행될 때" 특별한 역할을 한다.


Intance (인스턴스)

개념

  • Instance란?
    • 사전적 의미 : 예, 사례, 경우
    • 객체 지향 프로그래밍(OOP)에서 인스턴스(instance)는 해당 클래스의 구조로 컴퓨터 저장공간에서 할당된 실체를 의미

Instance

"생성자 함수가 반환해주는 빈 객체"를 흔히 Instance(인스턴스)라고 부른다.

function Person (name) {
  this.name = name;
}
let delilah = new Person ("jung eun");
let myPet = new Person ("willy";)

여기서 인스턴스라면 2번째 줄의 this가 만들어낸 결과값을 뜻하며 즉, 4번째,5번째 줄의 delilah, willy
=> "delilah는 Person의 인스턴스다" 라고 얘기한다.
=> delilah 와 willy는 Person의 인스턴스

JS의 "모든 인스턴스 객체"는 해당 객체의 생성자 함수의 프로토타입에 존재하는 속성 및 메소드에 접근하여 사용할 수 있다.

function Person (name) {
  this.name = name;
}
Person.prototype.age = 30;
let delilah = new Person ("jung eun");
console.log(delilah.age) // 30;

위 예제의 5번째 줄에서 인스턴스를 만들었다.
=> Person의 인스턴스를 만들어서, delilah라는 변수에 할당했다.
Person은 함수이며, 함수에는 always 프로토타입이라는 속성을 가지고 태어난다.
=> Person.prototype이라는 객체에 age라는 속성을 만들어주었다.
=> 이 경우, delilah 인스턴스는 직접적으로 본인이 age라는 속성을 가지지 않지만,
JS의 "모든 인스턴스 객체"는 해당 객체의 생성자 함수의 프로토타입에 존재하는 속성 및 메소드에 접근하여 사용할 수 있다.
라는 자바스크립트의 특징에 따라 delilah객체의 생성자함수인 Person의 프로토타입에 존재하는 age 속성에 접근할 수 있게 된다.

간단한 예)
-----------------------------------------
let arr = []; 	// 배열을 만들었다.
// arr을 만든 생성자 함수는? : Array
// Array 도 함수니까, 생성될 당시 prototype을 가지게 된다.
Array.prototype.myBaby = "willy";
console.log(arr);	// [] 	
console.log(arr.myBaby);	// "willy"  // arr은 여전히 빈 배열이지만, 프로토타입의 속성을 가져올 수 있다.

(최대한 손글씨를 안쓰는 이유... 좀 심하네 ㅋㅋ)

Prototype Chain

가족관계로 대입해서 설명해 보자.

  • 생성자 함수 : 남편 (constructor)
  • 모든 프로토타입 객체 : 아내 (prototype)
  • 자식 : 인스턴스 (instance)
  • _proto-- : 엄마
    (남편 : 아내 = 1 : 1)
function Daddy (name) {
  this.name = name;
}
Daddy.prototype.assistant = "yes";
  // Daddy.prototype도 객체라는 점을 알아두자! 
let child = new Daddy("jungeun");
console.log(child.assistant);	// "yes"
----------------------------------------
Object.prototype.helper = "grandma";
let arr = [];
console.log(arr.helper);	// "grandma"

1) 모든 자식은 자신의 것이 없는 경우에, 엄마로부터 속성을 빌려쓸 수 있다.
2) 본인 것이 있으면, 본인 것을 먼저 쓴다.
3) 엄마도 없으면, 엄마의 엄마(할머니)로부터 빌릴 수 있다.
// Daddy.prototype도 객체라는 점을 알아두자!

proto (던더프로토 (double underbar?))

: 엄마를 가리키는 것

function Person (name) {
  this.name = name;
}
let delilah = new Person ("jungeun");
const willy = Person.prototype === delilah.__proto__
console.log(willy);	// true
---------------------------------------
Array.prototype.__proto__ === Object.prototype;	// true
---------------------------------------
Object.prototype.__proto__;	// null (할머니가 끝)

쓸 필요 없다. 무엇인지 알고만 있자.

좀 많이 길어지고 있지만.. 끊지말고 계속 가야할 것 같다 ㅎㅎ
프로토타입과 연관된 개념인 Inheritance 상속에 대해서 알아보자.

Inheritance (상속)

하위계층들이 상위 계층들의 속성을 가져와서 쓸 수 있도록 하는 자바스크립트 특징
왜 쓰는지 그리고 어떻게 쓰는지 코드를 통해 알아보자.

1)   function Animal (name) {
2)     this.name = name;
3)   }
4)   Animal.prototype.sleep = function () {
5)     console.log("sleep");
6)   }
7)  
8)   function Human (name, language) {
9)     this.name = name;
10)    this.language = language;
11)  }
12)  Human.prototype.sleep = function () {
13)    console.log("sleep");
14)  }
15)  Human.prototype.write = function () {
16)    console.log("write");
17)  }
18)  
19)  let myPet = new Animal ("willy");
20)  let delilah = new Human ("jungeun", "korean");
--------------------------------------------
// 공통되는 코드들이 눈에 보인다. 
// 객체 안에서 다시 하나의 특징으로 분류될 수 있는 객체들이 나뉘는 경우가 매우 많다. 이때 일일히 코드를 중복해서 쓰는 것은 매우 번거롭고 비효율적인 일이다. 
// 하나씩 중복되는 것들을 찾아 제거해보자.
--------------------------------------------
1. 9)2)와 중복된다. -> 2)를 가져와야 하는데..?
2. 9)에 call or apply를 통해서 가져올 수 있다.
 2-1. call은 첫번째 인자로부터 받은 값을 해당 함수의 this로 설정하여 함수를 실행한다.
 2-2. 두번째 인자부터 나머지 인자들은, 해당함수의 인자로 전달된다. (인자의 개수 제한이 없음)
 2-3. apply는 첫번째 인자로부터 받은 값을 해당 함수의 this로 설정하여 함수를 실행한다.
 2-4. 두번째 인자는 반드시 "배열의 형태"여야 하며, 해당 배열의 요소들이 해당 함수의 인자로 전달된다. (인자의 개수는 단 두개)
 2-5. 둘 다, 해당 메소드가 사용된 함수를 실행시킨다.
3. 9)에서 함수 메소드 call은 call이 사용된 함수를 실행시킨다.
4. 사용된 함수는 20)에 있다. Human이라는 함수는 new Human으로 실행되었다.
5. 따라서, 8)의 함수 Human의 this는 Human의 인스턴스가 된다.
6. 9) 10)this 둘다 해당된다.
7. 9)에서 Human 인스턴스를 Animal이라는 함수를 실행하려고 하는데, 여기에 this를 설정해서 실행하게 된다.
8. 1)로 가서 Animal함수가 실행되는데, call메소드는 첫번째 인자값(여기서는 9)this)해당함수(Animal)의 인자로 전달한다. 
9. 2)에서 Animal함수가 또 this를 가지고 있는 것을 확인한다.this가 무엇인지 찾으려면, Animal함수가 어떻게 실행되었는지를 찾아야 한다.
10. 10)에서 call메소드를 사용하여 Human의 인스턴스로 지정해서 실행한 사실을 찾았다.
11. 따라서, 2)this는 Human의 인스턴스로 지정이 되어서 실행되었음을 알 수 있다. 
12. 플러스, name이라는 속성을 만들어 주었다.
13. 이 인자는 다시 9.1)로 돌아와서 call메소드의 인자로 넣어줄 수 있다.
--------------------------------------------
1)   function Animal (name) {
2)     this.name = name;
3)   }
4)   Animal.prototype.sleep = function () {
5)     console.log("sleep");
6)   }
7)  
8)   function Human (name, language) {
*9)    Animal.call(this); // 아래처럼 변경
*9.1)  Animal.call(this, name);
10)    this.language = language;
11)  }
12)  Human.prototype.sleep = function () {
13)    console.log("sleep");
14)  }
15)  Human.prototype.write = function () {
16)    console.log("write");
17)  }
18)  
19)  let myPet = new Animal ("willy");
20)  let delilah = new Human ("jungeun", "korean");
--------------------------------------------
// 하나더 겹치는 내용이 있다. => sleep 
// 동물을 남겨놓자.
--------------------------------------------
1. 12) 13) 14)를 제거한다.
2. *12)에서 해주는 작업은, 
  -> Human.prototype을 먼저 찾고 
  -> 없으면, Animal.prototype 
  -> 여기도 없으면, Object.prototype으로,, 순차적으로 올라가면서 찾고자 한다.
3. Object.create 에 대해서 아래에 정리해 놓았다. 잠시 알아보자.
4. 돌아와서, *12)에서 반환해 주는 값은? "빈객체"
5. 이 새로생긴 "빈객체"의 prototype은? Animal.prototype
6. 더해주어야 할 작업이 남아있는데, constructor 속성을 붙여 주어야 한다.
7. *13)에서 원래의 constructor속성을 재확인해준다. (자바스크립트는 완벽한 언어가 아니다ㅎ)
8. *12) *13) 는 쌍으로 붙어다닌다.
--------------------------------------------
1)   function Animal (name) {
2)     this.name = name;
3)   }
4)   Animal.prototype.sleep = function () {
5)     console.log("sleep");
6)   }
7)  
8)   function Human (name, language) {
*9)    Animal.call(this); // 아래처럼 변경
*9.1)  Animal.call(this, name);
10)    this.language = language;
11)  }
*12) Human.prototype = Object.create(Animal.prototype); // 연결고리 역할
*13) Human.prototype.constructor = Human // nessesary한 과정
//12)  Human.prototype.sleep = function () {
//13)    console.log("sleep");
//14)  }
15)  Human.prototype.write = function () {
16)    console.log("write");
17)  }
18)  
19)  let myPet = new Animal ("willy");
20)  let delilah = new Human ("jungeun", "korean");

Object.create

: 객체를 만드는 메소드로, 객체를 만들어서 반환(return)한다.

  • Object.create는 무조건 빈 객체를 만들어준다.
  • 인자를 프로토타입으로 갖는 객체를 만들어준다.
let obj = {a:"Apple"};
let newObj = Object.create(obj); // newObj 의 prototype이 obj가 된 것!
// Object.create라는 메소드에 인자(obj)를 넣어주면,
// Object.create 메소드가 결과적으로 만드는 "빈 객체"
// "빈 객체"의 프로토타입이, 인자가된다.
// {}.prototype === 넣어준 인자(여기서는 obj)
newObj;		// {};
newObj.a;	// "Apple"

하 일단 이렇게 마무리
언제든 피드백은 사랑입니다 >__<

이거는 문제편에 넣자 ㅋㅋ

socrative

1번)
같은 곳을 참조하게 됨
Object.create(@)
@에 들어가는 인자는 무엇인지?
부모 객체, 프로토타입 속성을 가진 새로운 객체

2번)
...
5번)

function A() {
  this.name = "A";
}
function B() {
  this.name = "B";
}
function C() {
  this.name = "C";
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

A.prototype.SAYHI = function () {console.log("hi")}
B.prototype.SAYHI = function () {
oconsole.log("hello")}

let a = new A();
let b = new B();
let c = new C();

c.SAYHI() // "hello"

결과//
B.prototype.__proto__ === A.prototype
C.prototype.__proto__ === B.prototype
  • Object.assign은 딥카피

  • Object.create는 상속을 위한 프로토타입 체이닝

  • c instanceof B : c는 B의 인스턴스냐? // true

  • b instanceof C : b는 C의 인스턴스냐? // false

  • c instanceof A : c는 A의 인스턴스냐? // true

  • b instanceof A : b는 A의 인스턴스냐? // true

  • a instanceof B : a는 B의 인스턴스냐? // false

11버)
ㅊcall. / apply


prototype vs class

생성자 함수

  • 특징

    1. 객체를 만든다.
    2. 초기 상태를 setting한다.
  • 그냥 일반 함수지만, new를 붙이면, (기본적으로 setting된) "객체"를 리턴한다.

  • 기본적으로 세팅된 객체 중에서 보통 메소드(함수)는 밖으로 빼서 좀 더 재사용성을 높이는데,
    "생성자 함수.prototype.함수명 = function () {}"
    하게 되면, 해당 생성자함수가 만드는 원형(prototype) 메소드는 해당 생성자함수가 만든 애들이 모두 사용할 수 있게 된다. (본인에게 똑같은 메소드가 있으면 본인 거를 먼저 사용한다.)

class

  • constructor function의 대체제
  • class는 객체를 만드는 공장이다.
  • ECMA6 이상에서 지원

상속 👇️

class Person {
  constructor (name, first, second) {
    this.name = name;
    this.first = first;
    this.second = second;
  }
  sum () {
    return this.first + this.second
  }
}
class PersonPlus extends Person {
  avg () {
    return (this.first + this.second) / 2
  }
}
let delilah = new PersonPlus("lee", 10, 20)
console.log(delilah.sum()) // 30
console.log(delilah.avg()) // 15

super keyword 👇️

  • 부모의 기능을 그대로 가져온다 / 호출시
class Person {
  constructor (name, first, second) {
    this.name = name;
    this.first = first;
    this.second = second;
  }
  sum () {
    return this.first + this.second
  }
}

/// 위의 클래스를 바꿀 수 없는 상황이며, 세번째 값을 더해주고 싶다고 가정한다.

class PersonPlus extends Person {
  constructor (name, first, second, third) {
    super(name, first, second)
    this.third = third
  }
  sum () {
    return super.sum() + this.third
  }
  avg () {
    return (this.first + this.second + this.third) / 3
  }
}
let delilah = new PersonPlus("lee", 10, 20, 30)
console.log(delilah.sum()) // 60
console.log(delilah.avg()) // 20

부모 자식 관계를 지정하는 두가지 방법

  • __proto__
  • Object.create() => 권장

proto (던더프로토)

  • __proto__를 통해, 엄마 객체를 직접 지정해 줄 수도, 바꿔줄 수도 있다.
let superObj = {super : "super"}
let subObj = {sub : "sub"}
subObj.__proto__ = superObj
// __proto__ 를 통해, 엄마 객체를 직접 지정해 줄 수도, 바꿔줄 수도 있다. (매우 유연)

console.log(subObj.sub) // sub
console.log(subObj.super) // super
subObj.super = "supppper"
console.log(subObj.super) // supppper

Object.create()

let superObj = {super : "super"}
let subObj = Object.create(superObj) ( === subObj.__proto__ = superObj)
// subObj는 superObj를 부모로 하는 새로운 객체이다.
subObj.sub = "sub" ( === let subObj = {sub : "sub"} )

call, apply, bind

bind에 대한 영상 (생활코딩_youtube)


출처 : 생활코딩💚️

profile
delilah's journey

0개의 댓글