😎 프로토타입은 강력하다.
https://xionwcfm.tistory.com/134
예전에 이미 프로토타입을 한번 정리해두었지만..
거의 까맣게 기억이 리셋되었기도 하고 제대로 이해하지 못한 상태에서
그렇구나..하면서 옮겨 적은 내용이 대부분이었습니다.
따라서 이전 클로저와 마찬가지로 프로토타입이 무엇인가라는 질문을 받았을 때
당황하지 않는 사람이 되는 방법을 적어보고자 합니다.
프로토타입은 말그대로의 의미를 가지는데
위키백과에서는 원래의 형태 또는 전형적인 예, 기초 또는 표준이라고 표현하고있습니다.
자바스크립트에서도 비슷한 의미를 지니는데요
😎프로토타입을 알기전에 알아야할 것
그것은 바로 생성자 함수입니다.
생성자 함수는 new 키워드와 함께 함수를 호출할 수 있는 함수이며
객체를 생성하는 함수를 말합니다.
😎아니 그럼 생성자 함수 쓰면 되지 프로토타입은 왜 쓰나요?
function Circle (name) {
this.hi = function() {
console.log("안녕하세요")
}
}
let a = new Circle(10)
let b = new Circle(10)
a.hi() // 이 메서드와
b.hi() // 이 메서드는 같은 코드로 이루어져있지만 다른 메서드다.
근데 생성자 함수엔 치명적인 문제가 있습니다.
우리는 array.map()와 같은 메서드들을 잔뜩 사용하고 있습니다.
근데 생성자 함수 방식으로 생성을 하면
똑같은 일을 하는 똑같은 array.map메서드가 객체를 생성하는 만큼 생성된다는 것입니다.
이건 굉장히 비효율적입니다.
😎모든 함수는 callable 하지만 모든 함수가 construct는 아니다.
무슨 소리일까요?
한국말로 치환하면 모든 함수는 호출 할 수 있지만
모든 함수가 생성자 함수가 될수 없다는 뜻입니다.
메서드와 화살표함수는 constructer가 아닙니다.
즉 메서드와 화살표함수에는 new 연산자를 붙여서 생성자 함수로 사용할 수 없다는 뜻입니다.
그런데 일반적인 의미로의 메서드와 ECMAScript에서 정의하는 메서드가
조금... 다르다는 점만 주의하면 되겠습니다.
ES6에서의 메서드는 메서드 축약 표현만을 의미합니다.
메서드 축약 표현은 뭘까요?
😎 메서드 축약 표현
const obj = {
x : function() {},
hi(){}
}
let xcon = new obj.x() // x {}
let hi = new obj.hi() // obj.hi is not a constructor
x라는 키값안에 함수를 넣어주는 위 예시는
일반적인 의미로는 obj의 메서드로서 사용되기때문에 메서드라고 볼 수도 있습니다.
하지만 ECMAScript에서 인정하는 메서드 축약 표현은
hi() {} 와 같은 형식으로 작성된 메서드 뿐입니다.
따라서 new obj.x는 정상적으로 동작하지만
new obj.hi는 constructor가 아니라는 오류가 발생합니다.
😎new.target은..?
function Circle (name) {
console.log(new.target)
}
let a = new Circle() // new.target === [Function: Circle]
let b = Circle() // new.target === undefined
function Circle (name) {
if(!new.target) return new Circle(name)
console.log(new.target)
}
쉽게 보면 생성자 함수로 사용하려고 만든 함수가 그냥 호출되는것을 대비해 만든 문법입니다.
위 예제를 보면 이해가 쉬운데
생성자 함수에 아래 코드예제처럼
단항연산자 !를 통해 트루시한 값이 되었다면 같이 받은 인자를 넣고
재귀 호출을 하는 것을 통해 new 키워드 없이 사용했어도
생성자 함수로 동작하게끔 코드를 바꿔줄 수 있습니다.
😎이제 프로토타입을 배울 준비가 끝났다.
생성자 함수란게 있는 것을 알았고.
생성자 함수는 new를 붙여서 호출한 함수라는 것을 알았으며
또한 생성자 함수가 가지는 고질적인 문제점에 대해서도 체득했습니다.
아주 멋지게도 프로토타입은 이 생성자함수의 문제를 해결할 방법을 가지고 있습니다!
😎자바스크립트에는 클래스가 없(었)다
function a (){}
let b = {}
console.log(a.hasOwnProperty('prototype')) // true
console.log(b.hasOwnProperty('prototype')) // false
하지만 자바스크립트는 객체 기반의 언어입니다.
클래스는 없(었)지만 객체지향이 가지는 멋진 재사용성을 자바스크립트는
프로토타입을 통해 구현하는데요.
(지금은 클래스 문법이 생겼다.. 하지만 본질은 프로토타입으로 굴러가는..)
우선 이건 알아야만 하는 것이. 자바스크립트에서는 "거의" 모든 것이 객체입니다.
함수조차도 객체이며 그 함수는 객체 중에서도 일급 객체입니다.
그리고 프로토타입은 생성자 함수 객체만이 소유하는 특권이라는 것이다.
아까 메서드축약표현과 화살표함수는 생성자함수가 될 수 없다고 했습니다.
이 prototype 프로퍼티는 hawOwnProperty()메서드를 이용해 확인할 수 있는데요.
위 코드 예제는 함수가 prototype 프로퍼티를 가지는 것을 확인하는 예제입니다.
😎객체 생성 방식에 따라 프로토타입도 달라진다.
let obj = {} // 객체리터럴
let conobj = new Object() // new Object 오브젝트 생성자
class Hi { // 클래스 문법을 활용
constructor(name) {
name = this.name
}
}
let obj3 = Object.create(null) // Object.create()메서드 사용
function hello(name){ //생성자 함수를 이용
this.name = name
}
let hel = new hello('엄')
객체의 생성 방식은 의외로 아주 다양합니다.
총 다섯가지 방식이 존재하는데 위 예제 코드가 다섯가지 방식을 각각 사용한 코드입니다.
1. 객체 리터럴 방식을 통한 생성
2. Object 생성자를 이용한 방법
3. 클래스 문법을 이용한 방법
4. Object의 내장메서드 create()를 사용하는 방법
5. 생성자 함수를 이용한 방법
😎OrdinaryObjectCreate
의 동작 방식은 다음과 같습니다. 추상연산 OrdinaryObjectCreat은
Object 생성자 함수에 인수를 전달하지않거나. undefined, null을 인수로 전달하며 호출하면
내부적으로는 OrdinaryObjectCreate 직역하면 평범한객체생성을 호출하여
평범한 프로토타입을 가지는 객체를 생성합니다.
이 얘기를 왜하느냐... 우리가 가장 흔하게 사용하는 객체 리터럴 방식을 이용한 생성을 하는 경우와
밀접하게 연관이 되어 있습니다. 우린 굳이 new Object()같은 걸 하지 않고 그냥 {} 로
객체를 생성함을 나타냅니다. 이렇게 객체리터럴로 생성하는 경우
내부적으로는 평범객체생성을 호출하여 부모 프로토타입을 Object.prototype으로 가지게 한다는 뜻
그럼 Object 생성자 함수에 의해 생성되면 어떨까요?
객체 리터럴과 마찬가지로 평범객체생성을 호출합니다.
즉 Object 생성자 함수 호출 방식으로 만들면
평범객체생성에 의해 부모 프로토타입은 Object.prototype입니다.
그럼 생성자 함수에 의해 생성된 객체의 프로토타입은 어떨까요?
이 경우에도 역시 평범객체생성을 호출합니다.
다만!!!!!!!!!!!!!!!!!!!
이때 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에
바인딩 되어있는 객체입니다.
따라서 Person 생성자 함수를 만들었다고 가정해보고
그 생성자 함수에 의해 생성된 객체의 프로토타입은 Person.prototype 이라는거죠!!
이건 위 두가지 경우와 전혀 다릅니다.
그럼 Object.create메서드를 사용하는 경우는 어떨까요?
이때엔 매개변수로 넘겨준 인자를 프로토타입으로 가지는 객체를 생성합니다.
따라서 이걸 이용해서 프로토타입이 null인 객체도 만들 수 있어요
그럼 클래스는요..?
생성자 함수에 의해 생성된 객체의 프로토타입과 비슷한 로직으로 동작합니다.
부모 클래스의 프로토타입을 상속받아요
😎모든 객체는 하나의 프로토타입을 갖는다.
...물론... Object.create()메서드에 null을 인자로 전달해주는 경우
그 Object의 프로토타입은 null(비어있음) 이기때문에
그 객체엔 프로토타입이 없다고도 볼 수 있....지만...........
아무튼 하나의 프로토타입을 가진다.
😎프로토타입이 멋있는 점
function Circle(radius) {
this.radius = radius
}
Circle.prototype.getArea = function() {
return this.radius
}
const circle2 = new Circle(2)
const circle1 = new Circle(1)
console.log(circle1.getArea === circle2.getArea) // true
Circle의 prototype에 getArea라는 프로퍼티를 만들고 함수를 할당해줬습니다.
이 메서드를 새로 생성한 인스턴스 circle2 circle1이 같이 공유한다는 걸 아래
console.log()를 통해 확인할 수 있습니다.
😎프로토타입 체인의 종점
Object.prototype이다.
또한 프로토타입 체인은 단방향 링크드 리스트로 구성되어야한다.
왜냐하면.. 서로를 참조하는 순환 참조가 일어나는 경우
프로토타입 체인이 무한 순환에 빠져 종점이 존재하지 않게되어 버리기 때문이다.
😎프로토타입의 생성시점
프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됩니다.
그런데 이 생성자 함수에는
내가 정의해서 쓰는 사용자 정의 생성자 함수와
빌트인 생성자 함수를 구분할 수 있습니다.
사용자 정의 생성자 함수는 내가 생성자 함수를 생성하는 시점에 프로토타입이 생성됩니다.
이는 빌트인 생성자 함수도 마찬가지입니다.
다만!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성되며
생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩됩니다.
생성자 함수를 생성하는 시점에 프로토타입이 생성되는데...
빌트인 생성자 함수는 전역 객체가 생성될 때 생성되니.. 어찌보면 당연하기도 합니다.
😎프로토타입 체인이 뭔데요
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks!`);
}
}
const myDog = new Dog('Fido', 'Labrador Retriever');
프로토타입은 각 객체마다 단 한개만 가질 수 있고..
프로토타입 체인은 단방향 링크드리스트이고..
여기까지는 다들 이해할거라 생각합니다.
프로토타입 체인의 개념은 스코프 체인과 매우 유사합니다.
참조하려는 프로퍼티가 없으면 부모 프로토타입의 프로퍼티를 순차적으로 검색하는 것이
프로토타입 체인이다. 라는 것
또 반대로 보면
myDog는 Dog 클래스에게 상속받고 dog 클래스는 또 Animal을 상속받고
또 Animal은 Object.prototype의 상속을 받으며
상위 프로토타입들의 메서드,프로퍼티들을 맨 아래층 애들은 프로토타입 체인을 이용해
사용할 수 있어지는... 것이다
또한 재밌는 점은 위와 같이 상위 클래스에서 정의한 메서드를
하위 클래스에서 재 정의할 경우 상위 클래스의 메서드는 가려지고
하위 클래스에서 재 정의한 메서드를 사용하게 된다는 것
물론 그렇다고 상위 클래스의 메서드가 소멸한 건 아닙니다.
😎프로퍼티 섀도잉과 오버라이딩
프로퍼티 섀도잉은 하위 클래스가 상위 클래스의 메서드를 재정의 하여 사용하는 경우.
상위 클래스의 메서드가 하위 클래스의 메서드에 의해 가려지는 현상을 말합니다.
반대로 오버라이딩은 상위 클래스가 갖고있는 메서드를
하위 클래스가 재정의하여 사용하는 방식을 말합니다.
오버라이딩을 하면 프로퍼티 섀도잉이 일어나는 구조인거죠
😎오버로딩은 또 뭐임
오버로딩은 함수의 이름은 동일하지만 매개변수의 타입, 개수가 다른 메서드를 구현하고
매개변수에 의해 메서드를 구별하여 호출하는 방식입니다.
자바스크립트에서는 지원하지는 않지만
arguments 객체를 이용해 비슷하게 구현할 수는 있습니다.
😎정적메서드
검색해보세요
반응형
'javascript' 카테고리의 다른 글
너무 재미있는 예제로 알아보는 실행컨텍스트 (0) | 2023.03.05 |
---|---|
getter,setter까지 복사하는 deep copy를 구현하자 (0) | 2023.03.02 |
v8 엔진으로의 딥다이브 (0) | 2023.02.27 |
자바스크립트의 모듈을 내가 만들어서 써보자! (1) | 2023.02.24 |
DOM을 다루는 실전적인 방법 (0) | 2023.02.14 |