프로토타입?
• 실제 면접에서 자바스크립트에 대해 물어보는 대표적인 질문인 “프로토타입”이라는 단어는
일반적인 의미로 보면 어떤 제품을 만들기 전의 시제품이라는 의미가 있는데 자바스크립트 객체의 시제품 정도로 이해하면 된다.
자바스크립트에서 함수를 선언하면 자동으로 그 함수를 위한 프로토타입 객체가 만들어 지는데
여기서 중요한 점은 함수를 이용해 객체를 생성해야 만들어지는 것이 아니라 함수를 선언하는 것만으로
자동으로 프로토타입 객체가 만들어 지는 것이다.
• 프로토타입 객체에 자동으로 constructor 라는 생성자가 추가되어 있다.
자바스크립트에서 함수를 이용해 객체를 생성한다는 의미는 사실 내부적으로 프로토타입의 생성자를 이용하는 것이며 User 라는 이름의 함수가 있고 이 함수를 이용해 new User() 로 객체를 생성하게 되면 내부적으로는 User함수의 프로토타입 객체내의 constructor() 라는 생성자가 이용되고 이 생성자에 의해 객체가 생성되는 것이다.
• 프로토타입 객체는 객체 생성을 위한 생성자를 제공하는 것 이외에 멤버를 추가할 수도 있다.
함수의 객체는 프로토타입 이용해 생성하게 됨으로 프토토타입에 추가된 멤버는 그 함수를 이용해 생성되는 여러 객체에서 필요한 공통의 멤버이며 여러 객체에서 공유하는 멤버이다.
프로토타입에 멤버를 등록할 때는 “함수명.prototype.멤버명” 이며 이렇게 등록된 프로토타입의 멤버를 함수의 객체에서 이용할 때는 “객체명.멤버명” 이다.
//test1- 함수와 프로토타입
function myFun(arg1, arg2){
console.log(arg1, arg2)
}
//일반함수로 준비
// myFun 내에 prototype 변수를 선언한 적이 없는데 에러 발생하지 않았다
//constructor() 를 가지고 나머지 멤버는 없는 빈 상태의 객체가 자동 준비
//생성자 함수가 아닌 모든 함수내에 준비
console.log(myFun.prototype)
//test2- 생성자 함수와 프로토타입
function User(name, age, address){
//this.xxx -> 객체가 생성될때마다 객체의 메모리에 유지
this.name = name
//프로토타입에 멤버 선언. 객체가 몇개 생성되든 상관없이 한곳에만 유지. 객체간 공유
User.prototype.age = age
User.prototype.address = address
}
let user1 = new User('홍길동', 20, 'seoul')
console.log(user1.name, user1.age, user1.address)//홍길동 20 seoul
let user2 = new User('김길동', 30, 'pusan')
console.log(user2.name, user2.age, user2.address)//김길동 30 pusan
console.log(user1.name, user1.age, user1.address)//홍길동 30 pusan
console.dir(user1)
console.dir(user2)
//test3- prototype 의 변수에 객체로 값을 대입해서 바꾸면
user1.age = 40
user1.address = 'incheon'
//prototype 에 선언된 변수 값을 객체에서 변경을 시키면
//prototype 의 데이터가 변경되지 않고 그 객체내에 동일 이름의 변수가 선언되어
//그 객체내에 유지되는 데이터가 된다.
console.log(user1.name, user1.age, user1.address)//홍길동 40 incheon
console.log(user2.name, user2.age, user2.address)//김길동 30 pusan
console.dir(user1)
console.dir(user2)
//생성자 함수 내에서만 prototype 멤버가 추가되는 것이 아니라 외부에서 추가 가능
User.prototype.email = 'a@a.com'
console.log(user1.name, user1.age, user1.address, user1.email)//홍길동 40 incheon a@a.com
//객체의 멤버명과 프로토 타입의 멤버명이 동일할 수도 있다
//우선으로 객체 멤버가 이용
//명시적으로 이름이 동일할때 프로토타입의 멤버를 참조하게 하고 싶다면?
console.log(user1.age, user1.__proto__.age)//40 30
프로토타입과 this
• 객체를 위한 프로퍼티와 함수가 선언되어야 하는데
객체의 this 멤버로 선언할 수도 있고 프로토타입 멤버로 선언할 수도 있다.
• this 멤버나 프로토타입 멤버를 모두 객체에서 이용할 수는 있지만 이 둘은 차이가 있는데
this 의 멤버는 객체별로 다른 데이터가 유지되지만 프로토타입의 멤버는
여러 객체가 만들어져도 모두 동일 데이터를 가지게 된다.
this 는 객체가 생성될 때마다 객체별로 메모리가 별도로 할당되기 때문에 객체별로 다른 값을 유지할 수 있는 것이며
프로토타입은 여러 개의 객체가 만들어진다고 하더라도 객체끼리 공유된다.
• this 와 프로토타입에 동일 이름의 멤버 선언이 가능하며
이 경우 객체로 멤버에 접근하면 this 에 선언된 멤버가 이용된다.
객체로 어떤 멤버에 접근하게 되면 자신의 메모리에서 찾게 되고 없는 경우에만 내부 참조를 이용해 프로토타입의 멤버에 접근한다.
• 프로토타입과 this 에 동일한 이름의 멤버가 선언이 가능하며
이를 이용해 객체들끼리 공유 해야 하는 멤버를 프로토타입으로 선언한 후에
어떤 특정 객체에서 프로토타입에 선언한 멤버를 다시 자신의 this 에 선언해서 특정 객체만 다른 값 혹은 로직을 가지도록 할 수 있다.
function User(name){
this.name = name
User.prototype.point = 20
User.prototype.sayHello = function(){
console.log(`Hello, ${this.name}, ${this.point}`)
}
}
let user1 = new User('honggildong')
user1.sayHello()//Hello, honggildong, 20
let user2 = new User('이길동')
//프로토타입에 선언된 멤버를 자신의 객체에서만 다른 값, 다른 로직으로 사용하고 싶다는 의도
//this 객체에 추가
user2.point = 30
user2.sayHello = function(){
console.log(`안녕하세요 ${this.name}, ${this.point}`)
}
user2.sayHello()//안녕하세요 이길동, 30
let user3 = new User('kimgildong')
user3.sayHello()//Hello, kimgildong, 20
프로토타입 – 메모리 효율성
• 프로토타입을 이용하게 되면 메모리 효율성 측면에서도 이점을 얻을 수 있다.
• 주로 이 경우는 프로퍼티보다는 함수를 선언하는 경우인데 동일 타입의 객체가 여러 개 생성되는 경우
각 객체에 등록되는 함수는 거의 대부분 로직이 동일한 것이 일반적이다.
그런데 이 함수를 this 에 선언하게 되면 동일한 동작을 하는 함수가
객체별로 매번 메모리에 할당되어 메모리가 불필요하게 점유되는 상황이 발생할 수도 있다.
"use strict"
function User1(name){
this.name = name
this.sayHello = function(){
console.log(`Hello, ${this.name}`)
}
}
let user1 = new User1('홍길동')
let user2 = new User1('이길동')
user1.sayHello()//Hello, 홍길동
user2.sayHello()//Hello, 이길동
//user1 과 user2 에 있는 sayHello 가 동일 함수인가?
console.log(user1.sayHello == user2.sayHello)//false
function User2(name){
this.name = name
User2.prototype.sayHello = function(){
console.log(`Hello, ${this.name}`)
}
}
let user3 = new User2('홍길동')
let user4 = new User2('김길동')
user3.sayHello()//Hello, 홍길동
user4.sayHello()//Hello, 김길동
console.log(user3.sayHello == user4.sayHello)//true
console.dir(user1)
console.dir(user2)
console.dir(user3)
console.dir(user4)
프로토타입 – 상속 구현
• 객체지향 프로그래밍에서 상속이란 어떤 객체에 선언된 멤버를 그대로 이어받아
새로운 객체를 선언하는방법을 의미한다.
상속이 주는 이점은 여러가지가 있지만 코드의 재사용 측면에서 유용한 기법이며
객체지향 프로그래밍의핵심 기법 중 하나이다.
또한 자바스크립트에서 상속을 이용한 코드의 재사용도 프로토타입을 이용해 구현할 수 있다.
▼ 상위 객체를 하위 프로토타입으로 지정
• 프로토타입도 객체이며 상위 함수의 객체를 하위 함수의 프로토타입으로 지정해서
상위에 선언된 것을 하위에서 이용하게 할 수 있다.
▼ 상위의 프로토타입을 하위 프로토타입으로 지정
• 상위 객체를 프로토타입으로 이용하는 방법은 상위 객체가 생성되어야 하는데,
상위 객체가 생성되어 이용되어야 하거나 아니면 함수의 프로토타입들이 별도로 유지되어야 하는 경우는
이 방법을 이용할 수도 있지만 불 필요하게 객체가 생성될 수도 있다.
상위의 프로토타입을 그대로 하위에서 프로토타입으로 이용하게 할 수도 있다.
"use strict"
//공통 코드를 개발하는 개발자 입장에서
//다른 개발자들이 만드는 모든 도형에 공통으로 들어가는 멤버
function Shape(name){
//생성자 함수, new Shape() 로 객체를 생성하게 되면 자동으로 객체가 하나 만들어지고
//생성자 함수를 거치면서 그 객체에 변수, 함수가 등록된다.
//객체 생성 시점에 자동으로 만들어지는 객체를 지칭
this.name = name
}
Shape.prototype.draw = function(){
console.log(`${this.name} 도형을 그립니다.`)
}
console.dir(new Shape('react1'))
//후배 개발자 입장에서 선임 개발자가 만들어놓은 Shape 를 상속받아서
//자신이 구체적으로 만들어야 하는 도형을 개발하겠다는 입장
function Rectangle(name, width, height){
//name 데이터가 객체에 유지되어야 한다
//객체별로 name 데이터가 다르게 유지 => this 에 등록
//아래처럼 작성해서 객체별로 데이터가 유지되게 할수도 있지만
// this.name = name
//다른 생성자 함수에 name 이 작성, 그걸 그대로 이용
//자신이 호출되어 객체가 생성될때 다른 생성자 함수(상위)도 같이 호출되어
//자신의 this 에 그 생성자 함수(상위)에 등록된 멤버가 들어오게함
Shape.apply(this, [name])//name 을 매개변수로 지정해서 Shape생성자 함수 호출
//width, height 데이터가 객체에 유지되어 한다.
//객체 여러개에 공통 값인가? ==> prototype 에 등록
//객체별로 다르게 유지되어야 하는 값인가? ==> this 에 등록
this.width = width
this.height = height
}
let rect1 = new Rectangle('react1', 100, 200)
//name 을 Rectangle 에서 직접 준비하지 않았다.
//Shape 생성자 함수를 같이 실행시킨것. Shape 에 등록한 것이 Rectangle 에서
//자신것 처럼 사용하고 있다. ==> 상속 개념
console.log(rect1.name, rect1.width, rect1.height)//react1 100 200
//상위를 지정해서(Shape), 객체 생성 시점에 상위 생성자함수까지 호출이되었지만
//상위의 prototype 까지 상속되지는 않는다
// rect1.draw() //error
//어떤 생성자 함수를 상속하고자 할때 그 함수의 prototype 까지 상속되게 하고자 한다면?
Rectangle.prototype = new Shape()//prototype 교체
Rectangle.prototype.calcArea = function(){
console.log(`area : ${this.width * this.height}`)
}
let rect2 = new Rectangle('rect2', 100, 200)
console.log(rect2.name, rect2.width, rect2.height)//rect2 100 200
rect2.draw()//rect2 도형을 그립니다.
rect2.calcArea()//area : 20000
console.dir(rect2)
//this - name, width, height
//prototype - name, calcArea
//prototype - protypye - draw
//상위 프로토타입을 상속받고자 할때
//위처럼 상위 객체를 나의 프로토타입으로 등록해도 됨
//Rectangle.prototype = new Shape()
//상위의 프로토타입을 나의 프로토타입으로 지정해도 됨
Rectangle.prototype = Shape.prototype
Rectangle.prototype.calcArea = function(){
console.log(`area : ${this.width * this.height}`)
}
let rect3 = new Rectangle('rect3', 100, 200)
console.log(rect3.name, rect3.width, rect3.height)//rect3 100 200
rect3.draw()//rect3 도형을 그립니다.
rect3.calcArea()//area : 20000
console.dir(rect3)
//this = name, width, height
//prototype - draw, calcArea
'BootCamp Review' 카테고리의 다른 글
2024.10.01 (Day 18) - JavaScript OOP / 클래스(class) (0) | 2024.10.01 |
---|---|
2024.09.26-30 (Day 15,16,17) - JavaScript OOP / 다양한 OOP 기법 (0) | 2024.09.30 |
2024.09.24 (Day 13) - JavaScript OOP / 생성자 함수 (0) | 2024.09.24 |
2024.09.23 (Day 12) - JavaScript OOP / Object Literal (0) | 2024.09.24 |
2024.09.20-23 (Day 11, 12) - JavaScript Implicit Object 3 (0) | 2024.09.23 |