JavaScript는 클래스라는 개념이 없습니다. 그래서 기존의 객체를 복사하여(cloning) 새로운 객체를 생성하는 프로토타입 기반의 언어입니다. 프로토타입 기반 언어는 객체 원형인 프로토타입을 이요하여 새로운 객체를 만들어냅니다. 이렇게 생성된 객체 역시 또 다른 객체의 원형이 될 수 있습니다. 프로토타입은 객체를 확장하고 객체 지향적인 프로그래밍을 할 수 있게 해줍니다. 프로토타입은 크게 두 가지로 해석됩니다. 프로토타입 객체를 참조하는 prototype함수와 객체 멤버인 proto속성이 참조하는 숨은 링크가 있습니다. 이 둘의 차이점을 이해하기 위해서는 JavaScript 함수와 객체의 내부적인 구조를 이해 해야합니다. 이번 글에서는 JavaScrip의 함수와 객체 내부 구조부터 시작하여 프로토타입에 대해 알아보겠습니다.
https://www.nextree.co.kr/p7323/ 블로그를 참고하였습니다.
JavaScript : 프로토타입(prototype) 이해
JavaScript는 클래스라는 개념이 없습니다. 그래서 기존의 객체를 복사하여(cloning) 새로운 객체를 생성하는 프로토타입 기반의 언어입니다. 프로토타입 기반 언어는 객체 원형인 프로토타입을 이
www.nextree.co.kr
1. 함수와 객체의 내부 구조
JavaScript에서는 함수를 정의하고. 파싱단계에 들어가면, 내부적으로 수행되는 작업이 있습니다. 함수 멤버로 prototype 속성이 있습니다. 이 속성은 다른 곳에 생성된 함수이름의 프로토타입 객체를 참조합니다. 프로토타입 객체의 멤버인 constuctor 속성은 함수를 참조하는 내부구조를 가집니다. 아래의 그림과 같습니다.
function Person() {}
[ 소스 1 ]
속성이 하나도 없는 Person이라는 함수가 정의되고, 파싱단계에 들어가면, Person 함수 Prototype속성은 프로토타입 객체를 참조합니다. 프로토타입 객체 멤버인 constructor속성은 Person함수를 참조하는 구조를 가집니다. 여기서 알아야 하는 부분은 Person함수의 prototype속성이 참조하는 프로토타입 객체는 new 연산자와 Person함수를 통해 생성된 모든 객체의 원형이 되는 객체입니다. 생성된 모든 객체가 참조한다는 것을 기억애햐 합니다. 아래의 그림과 같이 표현합니다.
function Person() {}
var joon = new Person();
var jisoo = new Person();
[ 소스 2]
JavaScript에서는 기본 데이터 타입인 boolean, number, string, 그리고 특별한 값인 null, undefined빼고는 모두 객체입니다. 사용자가 정의한 함수도 객체이고, new라는 연산자를 통해 생성된 것도 객체입니다. 객체 안에는 proto(비표준)속성이 있습니다. 이 속성은 객체가 만들어지기 위해 사용된 원형인 프로토타입 객체를 숨은 링크로 참조하는 역할을 합니다
2. 프로토타입 객체란?
함수를 정의하면 다른 곳에 생성되는 프로토타입 객체는 자신이 다른 객체의 원형이 되는 객체입니다. 모든 객체는 프로토타입 객체에 접근할 수 있습니다. 프로토타입 객체도 동적으로 런타임에 멤버를 추가할 수 있습니다. 같은 원형을 복사로 생성된 모든 객체는 추가된 멤버를 사용할 수 있습니다.
function Person() {}
var joon = new Person();
var jisoo = new Person();
Person.prototype.getType = function () {
return "인간";
};
console.log(joon.getType()); // 인간
console.log(jisoo.getType()); // 인간
[ 소스 3]
위 소스 3 6라인은 함수 안의 prototype속성을 이요하여 멤버를 추가하였습니다. 프로토타입 객체에 getType()이라는 함수를 추가하면 멤버를 추가하기 전에 생성된 객체에서도 추가된 멤버를 사용할 수 있습니다. 같은 프로토타입을 이용하여 생성된 joon과 jisoo객체는 getType()을 사용할 수 있습니다.
여기서 알아두어야 할 것은 프로토타입 객체에 멤버를 추가. 수정, 삭제할 때는 함수 안의 prototype속성을 사용해야 합니다. 하지만 프로토타입 멤버를 읽을 때는 함수 안의 prototype 속성 또는 객체 이름으로 접근합니다.
function Person() {}
var joon = new Person();
var jisoo = new Person();
joon.getType = function () {
return "사람";
};
Person.prototype.getType = function () {
return "인간";
};
console.log(joon.getType()); // 사람
console.log(jisoo.getType()); // 인간
jisoo.age = 25;
console.log(joon.age); // undefined
console.log(jisoo.age); // 25
[ 소스 4]
위 소스 4 1라인은 joon객체를 이용하여 getType()리턴값을 사람으로 수정하였습니다. 그리고 joon과 jisoo에서 각각 getType()을 호출하면 joon객체를 이용하여 호출한 결과는 사람으로 출력되고, jisoo로 호출한 결과는 인간이 출력됩니다. 생성된 객체를 이용하여 프로토타입 객체의 멤버를 수정하면 프로토타입 객체에 있는 멤버를 수정하는 것이 아니라 자신의 객체에 멤버를 추가하는 것입니다. joon객체를 사용하여 getType()를 호출하면 프로토타입 객체의 getType()을 호출한 것이 아닙니다. joon객체에 추가된 getType()을 호출한 것입니다. 프로토타입 객체의 멤버를 수정할 경우 멤버 추가와 같이 함수의 prototype속성을 이용하여 수정합니다.
결론을 내리면, 프로토타입 객체는 새로운 객체가 생성되기 위한 원형이 되는 객체입니다. 같은 원형으로 생성된 객체가 공통으로 참조하는 공간입니다. 프로토타입 객체의 멤버를 읽는 경우에는 객체 또는 함수의 prototype속성을 통해 접근할 수 있습니다. 하지만 추가, 수정, 삭제는 함수의 prototype속성을 통해 접근해야 합니다.
프로토타입이란?
JavaScript에서 기본 데이터 타입을 제외한 모든 것이 객체입니다. 객체가 만들어지기 위해서는 자신을 만드는데 사용된 원형인 프로토타입 객체를 이용하여 객체를 만듭니다. 이때 만들어진 객체 안에 __proto__(비표준)속성이 자신을 만들어낸 원형을 의미하는 프로토타입 객체를 참조하는 숨겨진 링크가 있습니다. 이 숨겨진 링크를 프로토타입이라고 정의합니다.
function Person() {}
var joon = new Person();
[ 소스 5]
joon객체의 멤버인 __proto__(비표준)속성이 프로토타입 객체를 가리키는 숨은 링크가 프로토타입이라고 합니다. 프로토타입을 크게 두 가지로 해석된다 했습니다. 함수의 prototype속성은 프로토타입 객체를 참조하는 속성입니다. 그리고 함수와 new 연산자가 만나 생성한 객체의 프로토타입 객체를 지정해주는 역할을 합니다. 객체 안의 __proto__(비표준)속성은 자신을 만들어낸 원형인 프로토타입 객체를 참조하는 숨겨진 링크로써 프로토타입을 의미합니다.
JavaScript에서는 숨겨진 링크가 있어 프로토타입 객체 멤버에 접근할 수 있습니다. 그래서 이 프로토타입 링크를 사용자가 정의한 객체에 링크가 참조되도록 설정하면 코드의 재사용과 객체지향적인 프로그래밍을 할 수 있습니다.
4. 코드의 재사용
코드의 재사용 하면 떠오른 단어는 상속입니다. 클래스라는 개념이 있는 Java에서는 중복된 코드를 상속받아 코드를 재활용할 수 있습니다. 하지만 JavaScript에서는 클래스가 없는, 프로토타입 기반 언어입니다. 그래서 프로토타입을 이용하여 코드를 재사용할 수 있습니다.
이 방법에도 크게 두 가지로 분류할 수 있습니다. classical방식과 prototypal방식이 있습니다. classical방식은 new연산자를 통해 생성한 객체를 사용하여 코드를 재사용하는 방법입니다. 마치 Java에서 객체를 생성하는 방법과 유사하여 classical방식이라고 합니다. prototypal방식은 리터럴 또는 Object.create()를 이용하여 객체를 생성하고 확장해 가는 방식입니다. 두 가지 방법 중 JavaScript에서는 prototypal방식을 더 선호합니다. 그 이유는 classical방식보다 간결하게 구현할 수 있기 때문입니다. 밑의 1~4는 classical방식의 코드 재사용 방법이고, 5번은 prototypal방식인 Object.create()를 사용하여 코드의 재사용을 보여줍니다.
(1) 기본 방법
부모에 해당하는 함수를 이용하여 객체를 생성합니다. 자식에 해당하는 함수의 prototype속성을 부모 함수를 이용하여 생성한 객체를 참조하는 방법입니다.
function Person(name) {
this.name = name || "혁준";
}
Person.prototype.getName = function () {
return this.name;
};
function Korean(name) {}
Korean.prototype = new Person();
const kor1 = new Person();
console.log(kor1.getName()); // 혁전
const kor2 = new Korean("지수");
console.log(kor2.getName()); // 혁준
위 소스코드를 보면 부모에 해당하는 함수는 Person입니다. Korean함수의 prototype속성을 부모 함수로 생성된 객체로 바꾸어 주었습니다. 이제 Korean함수와 new연산자를 이용하여 생성된 kor객체의 __proto__속성이 부모 함수를 이용하여 생성된 객체를 참조합니다. 이 객체가 Korean함수를 이용하여 생성된 모든 객체의 프로토타입 객체가 됩니다. kor1에는 name과 getName()이라는 속성이 없지만, 부모에 해당하는 프로토타입객체에 name이 있습니다. 이 프로토타입객체의 부모에 getName()을 가지고 있어 kor1에서 사용할 수 있습니다. 이 방법에도 단점이 있습니다. 부모 객체의 속성과 부모 객체의 프로토타입 속성을 모두 물려받게 됩니다. 대부분의 겨웅 객체 자신의 속성은 특정 인스턴스에 한정되어 재사용할 수 없어 필요가 없습니다. 또한, 자식 객체를 생성할 때 인자를 넘겨도 부모 객체를 생성할 때 인자를 넘겨주지 못합니다. kor2객체를 생성할 때 Korean함수의 인자로 지수라고 주었습니다. 객체를 생성한 후 getName()을 호출하면 지수라고 출력될 거 같지만, 부모 생성자에 인자를 넘겨주지 않았기 때문에 name에는 default값인 혁준이 들어있습니다. 객체를 생성할 때마다 부모의 함수를 호출할 수도 있습니다. 하지만 매우 비효율적입니다.
(2) 생성자 빌려 쓰기
부모 함수의 this에 자식 객체를 바인딩하는 방식입니다
function Person(name) {
this.name = name || "혁준";
}
Person.prototype.getName = function () {
return this.name;
};
function Korean(name) {
Person.apply(this, arguments);
}
Korean.prototype = new Person();
const kor1 = new Person();
console.log(kor1.getName()); // 혁전
const kor2 = new Korean("지수");
console.log(kor2.getName()); // 지수
Korean 함수 내부에서 apply함수를 이용합니다. 부모객체인 Person함수 영역의 this를 Korean함수 안의 this로 바인딩 합니다. 이것은 부모의 속성을 자식 함수 안에 모두 복사합니다. 객체를 생성하고, name을 출력합니다. 객체를 생성할 때 넘겨준 인자를 출력하는 것을 볼 수 있습니다. 하지만 생성자 빌려 쓰기는 부모객체 멤버를 복사하여 자신의 것으로 만들어 버린다는 차이점이 있습니다. 하지만 이 방법은 부모객체의 this로 된 멤버들만 물려받게 되는 단점이 있습니다. 그래서 부모객체의 프로토타입 객체의 멤버들은 물려받지 못합니다. 위 그림을 보면 kor1객체에서 부모객체의 프로토타입 객체에 대한 링크가 없다는 것을 볼 수 있습니다.
function Person(name) {
this.name = name || "혁준";
this.gender = "male";
}
Person.prototype.getName = function () {
return this.name;
};
// Korean함수를 호출 => 인자가 Person함수의 인자로 들어감 그리고 Korean의 prototyp이 Person이니까 name으로 접근 가능.
function Korean(name) {
Person.apply(this, arguments);
console.log("korean this >> ", this);
}
// Korean.prototype = new Person();
const kor1 = new Korean("hyunseo");
console.log(kor1.gender);
// const kor1 = new Person();
// console.log(kor1.getName()); // 혁전
// const kor2 = new Korean("지수");
// console.log(kor2.getName()); // 지수
위와 같이 prototype = new Person()을 해제하게 되면 getName을 호출 할 수 없게 되며 Korean안에서 this를 찍게되면 진짜로 부모의 prototype을 제외한 속성들을 다 불러 오는 것을 볼 수 있습니다.
(3) 생성자 빌려 쓰고 프로토타입 지정해주기
그냥 prototype을 지정해주면 된다. 하지만 이 방법에도 문제가 있는데 부모 생성자를 두 번 호출한다는 것입니다. 생성자 빌려 쓰기 방법과 달리 getName()은 제대로 상속되었지만, name에 대해서는 kor1객체와 부모 함수를 이용하여 생성한 객체에도 있는 것을 볼 수 있습니다.
(4) 프로토타입 굥유
function Person(name) {
this.name = name || "혁준";
this.gender = "male";
}
Person.prototype.getName = function () {
return this.name;
};
function Korean(name) {
this.name = name;
}
Korean.prototype = Person.prototype;
const kor1 = new Korean("hyunseo");
console.log(kor1.getName()); // hyunseo
console.log(kor1.name); // hyunseo
자식 함수의 prototype속성을 부모 함수의 prototype속성이 참조하는 객체로 설정했습니다. 자식 함수를 통해 생성된 객체는 부모 함수를 통해 생성된 객체를 거치지 않고 부모 함수의 프로토타입 객체를 부모로 지정하여 객체를 생성합니다. 부모 함수의 내용을 상속받지 못하므로 상속받으려는 부분을 부모 함수의 프로토타입 객체에 작성해야 사용자가 원하는 결과를 얻게 됩니다.
function Person(name) {
this.name = name || "혁준";
this.gender = "male";
}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.gender = "male";
function Korean(name) {
this.name = name;
}
Korean.prototype = Person.prototype;
const kor1 = new Korean("hyunseo");
console.log(kor1.getName()); // hyunseo
console.log(kor1.name); // hyunseo
console.log(kor1.gender);
이렇게 부모의 prototype에 gender를 설정해줘야 비로소 사용이 가능하다는 뜻이다.
(5) prototypal한 방식의 재사용
var person = {
type: "인간",
getType: function () {
return this.type;
},
getName: function () {
return this.name;
},
};
var joon = Object.create(person);
joon.name = "혁준";
console.log(joon.getType());
console.log(joon.getName());
Object.create()를 사용하여 객체를 생성함 과 동시에 프로토타입객체를 지정합니다. 이 함수의 첫 번쨰 매개변수는 부모객체로 사용할 객체를 넘겨주고, 두 번쨰 매개변수는 선택적 매개변수로써 반환되는 자식객체의 속성에 추가되는 부분입니다. 이 함수를 사용함으로 써 객체 생성과 동시에 부모 객체를 지정하여 코드의 재활용을 간단하게 구현할 수 있습니다.
'React > ReactJs' 카테고리의 다른 글
React - FrontEnd Project - 1 (0) | 2022.03.15 |
---|---|
React - JWT (0) | 2022.03.13 |
React - mongoDB ( mongoose ) (0) | 2022.03.12 |
React - koa FrameWork (0) | 2022.03.09 |
나만의 Webpack 개발환경 만들기 plugin ( React ) (0) | 2022.02.27 |