자바스크립트는 10일만에 만들어진 프로토타입 기반 객체지향 프로그래밍 언어다. 자바스크립트에서 프로토타입이 얼마나 강력한지 알기 위해 프로토타입 개념을 더 자세히 학습하고자 한다. 감사하게도 정리가 잘 되어 있는 온라인 소스가 많아서 부분부분 참고하며 글로 풀어보려고 한다.
목표: 나 자신도 프로토타입 개념에 자신감을 가지고, 이 글을 보는 모든 사람이 이해할 수 있도록 simple Korean으로 풀어 설명하는 것이다.
그러면 객체지향 프로그래밍 언어란 뭘까? 객체지향 절차지향 등 어려운 용어가 상당히 많다. Simple Korean으로 풀어보면...
우리가 실생활에서 쓰는 모든 것, 상태와 행위를 객체라 한다. 이 객체들을 레고 블럭처럼 조립
해서 하나의 프로그램을 만드는 것
을 객체 지향 프로그래밍이라고 할 수 있다. 객체지향을 들으면서, 절자 지향 프로그래밍도 들어봤는데 차이점이 뭐지?
절차지향은
순차적으로 실행
에 초점이 되어있고
객체지향은관계/조직에
초점을 맞추고 있다는 초점의 차이일 뿐이다.
절차적 프로그래밍이라고 해서 객체를 다루지 않는 것이 아니고, 객체지향 프로그래밍이라고해서 절차가 없는 것도 아니다.
프로토타입 정의를 알아보기 전에, 그나마? 친근한 용어를 이용해 특징을 알아보는 것도 좋을 것 같다.
자바스크립트에서 단순 원시 타입(simple primitive)인 문자열, 숫자, 불리언, null, undefined를 제외한 모든 타입은 객체다. 그렇다. 자바스크립트에서는 배열, 객체, 함수도 객체다
.
JavaScript의 모든 객체(배열, 객체, 함수 등)는 상위 객체를 참조하며, 이 상위 객체를 프로토타입
이라고 한다. 일단 상위 객체를 참조한다는 말만 기억해두자.
위키디피아 정의 - 프로토타입 기반 언어는 클래스 기반 언어에서 상속을 사용하는 것과는 다르게, 객체를 원형(프로토타입)으로 하는 복제 과정을 통해 객체의 동작 방식을 재사용 할 수 있게 한다.
프로토타입 기반 언어는 원형(원본) 객체를 복제하여 새로운 객체를 하는 생성하는 언어를 말한다. 그런데 자바스크립트도 복제를 할까? 자바스크립트는 약간 다르다. 복제가 아닌 프로토타입 링크를 통해
원형(원본)을 참조한다.
적절한 비유일지는 모르겠지만...... 내가 이해한 프로토타입은 리액트와 어렴풋이 비슷하다.
리액트로 생각해보면, 부모와 자식사이에서 props나 함수를 공유한다.
-----> 프로토타입
도 부모 자식 사이의 공유와 비슷하다.
리액트에서도 부모 컴포넌트가 최상위고, 자식 컴포넌트들은 부모가 던져준 props나 함수를 사용할 수 있다.
----->자식은 부모가 공유해주는 prototype 함수
를 사용할 수 있다.
props drilling와 비슷하게 props를 위 아래로 넘겨줄 수 있는 체인 매커니즘을
-----> 프로토타입 체인
이라고 부른다.
리액트에서 map을 사용할 때 key 값이 필요하듯, 부모와 자식의 연결고리 key를
-----> _proto_
라고 부른다. _proto_
를 이용하여 상위를 참조한다.
let arr = [1,2,3];
let add = function(x,y){
return x + y;
}
console.dir(add.prototype.constructor); // ƒ add(x,y)
console.log(add.prototype.constructor === add); // true
(위에 코드 & 아래 사진 참조)
add 함수를 만들면, 자동으로 add.prototype.constructor가 생긴다. 신기한 부분은 add === add.prototype.constructor
이곳이다.
코드를 console.dir로 까보면 prototype 프로퍼티와
_proto_
용어가 나온다.
즉, add 함수를 생성할 때, (아래 사진 참조)
1. add 함수는 prototype 프로퍼티
와 _proto_
를 가지고 있다.
2. 자동으로 add prototype이 따로 생성 된다. 이 prototype 안에는 constructor가 있다.
3. add 함수에 prototype 프로퍼티는
-> 따로 생긴 add.prototype을 가르킨다.
4. add 함수 === add.prototype.constructor 연결이 된다.
함수가 가지고 있는 prototype 프로퍼티
와, 따로 생긴 prototype은 다르다.
add.setLastName = function(){
return 'Last: Kim';
}
add.prototype.setName = function(){
return 'Name: Frank';
}
console.log(add.setLastName());
console.log(add.prototype.setName());
ES5 이전에는 프로토타입 체인을 구현하려면 무조건 생성자 함수와 new 연산자를 사용해야 했다. 다행스럽게도 ES5부터는 Object.create라는 프로토타입 언어의 특징을 잘 살려 객체를 생성할 수 있는 새로운 방법을 제공한다. 함수면 체인 구현이 가능해보인다.
우리는 새로운 방법 Object.create를 사용해볼 것이다.
// ========= 프로토타입 체인 구현 =========
let copyCat1 = Object.create(add);
console.log(copyCat1); // Function {}
console.log(copyCat1.setLastName());
// 'Last: Kim'
console.log(copyCat1.prototype.setName());
// 'Name: Frank'
위와 같이 copyCat1은 setLastName, setName 함수를 따로 설정해주지 않아도 됐다. prototype의 특성 프로토타입 체인(체인 메커니즘)을 통해 상위를 참조해서 함수가 사용 가능해졌다.
프로토타입 기반이라는 의미는 위에서 언급한 것처럼, 상위 객체를 참조하며, 이 상위 객체를 프로토타입이라고 한다.
만약 99999999999개의 값을 바꾸라고...누군가..지시한다면...?
let PROTOTYPE = {
type: 'employee',
work: function() {},
}
function Company(name, position){
let who = {
name:name,
position: position,
}
who._proto_ = PROTOTYPE;
return who;
}
var name1 = Company('kim', 'developer');
var name2 = Company('lee', 'designer');
// ..... x99999999997개......
앞서 말한바와 같이 _proto_
는 key값 연결고리다. 저 연결고리 키값을 바꿔버리면... 값들이 한번에 바뀌는 현상을 볼 수 있다.
PROTOTYPE.type = 'Team'
console.log(name1._proto_.type); // 'Team'
name1._proto_.sleep = () => 'go to sleep';
console.log(PROTOTYPE);
// {type: 'Team', work: ƒ, sleep: ƒ}
console.log(name2._proto_);
// {type: 'Team', work: ƒ, sleep: ƒ}
이 메소드는 객체가 특정 프로퍼티를 자기만의 직접적인 프로퍼티로서 소유하고 있는지를 판단하는데 사용된다. in 연산과는 다르게, 이 메소드는 객체의 프로토타입 체인을 확인하지는 않는다.
const obj = {
ownProperty: 1
};
obj.hasOwnProperty("ownProperty"); // true
obj.hasOwnProperty("prototype"); // false
obj.newProperty = 1;
Object.prototype.fromProtoType = 'hey prototype';
console.log(obj.hasOwnProperty("newProperty")); // true
console.log(obj.hasOwnProperty("fromProtoType")); // false
console.log(obj); // {ownProperty: 1, newProperty: 1}
console.log(obj._proto_.fromProtoType); // 'hey prototype';
여기서 주의할 점!!! 자바스크립트는 프로퍼티 명칭으로서 hasOwnProperty()를 보호하지 않는다. hasOwnProperty 네이밍으로 장난치지 말라는 소리...
const messWithHasOwnProPerty = {
hasOwnProperty: function() {
return false;
},
careful: 'Be careful'
};
console.log(messWithHasOwnProPerty.hasOwnProperty('careful'));
// false
그렇다면... 우리는 hasOwnProperty를 언제 사용하면 좋을까?
prototype과 어떻게 다르게 써야할까?
왜 사용해야할까?
시간이 지나 코드를 변경해야할 수도 있고, 누군가 자신의 코드를 재사용할 수 도 있다. 하나의 예로 hasOwnProperty 를 사용하지 않은 채, 시간이 흘러 누군가 코드를 수정했다고 가정해보자.
const emptyObj = {};
console.log(emptyObj.getFirstName()); // 'Kim';
console.log(emptyObj.hasOwnProperty(getFirstName())); // false;
const who = {
name: 'Frank',
getName() {
return this.name;
}
};
console.log(who.name); // 'Frank'
console.log(who.getName()); // 'Frank'
Object.prototype.getFirstName = function(){
return 'Kim';
}
// Bad - 프로토타입 체인을 통해 Object 접근
if(who.getName){
console.log(who.getName()); // 'Frank'
}
// Bad - 프로토타입 체인을 통해 Object 접근
if(who.getFirstName){
console.log(who.getFirstName()); // 'Kim'
}
// Good - 고유의 프로퍼티인지 체크. 프로토타입 한번 거름.
if(who.hasOwnProperty("getName")){
console.log(who.getName()); // 'Frank'
}
// Good - 고유의 프로퍼티인지 체크. 프로토타입 한번 거름.
if(who.hasOwnProperty("getFirstName")){
console.log('Is it unique enough?');
} else {
console.log("Doesn't have getFirstName() as property");
// "Doesn't have getFirstName() as property"
}
- hasOwnPropety로 우리가 기대할 수 있는것은?
1. 예기치 못한 상황에 따른 버그 대비
2.코드의 가독성 향상
흠... 우리가 잘 알고 있는 오브젝트를 루핑할 때 쓰는 for in.
과연 for in은 프로토타입 체인으로부터 안전할까?
const emptyObjAgain = {};
for(let key in emptyObjAgain){
console.log(key); // undefined
}
Object.prototype.loopEmptyObj = function(){
return 'accessing prototype!'
}
for(let key in emptyObjAgain){
console.log(key); // loopEmptyObj
}
// ========== 방어로직을 세워볼까? ==========
for(let key in emptyObjAgain){
if(emptyObjAgain.hasOwnProperty(key)){
console.log(key);
} else {
console.log('Accesing prototype');
}
}
역시 자바스크립트는 유연한 언어다... ㅎㅎㅎ 조심 또 조심...
오호.. hasOwnProperty()와 비슷한 친구를 찾았다.
Object.hasOwn() Note:
Object.hasOwn() is intended as a replacement for Object.prototype.hasOwnProperty().
쓰임새: Object.hasOwn(obj, prop)
const friends = {
friend1: 'kim',
friend2: 'lee',
};
Object.prototype.makeNewFriends = function(){
return `accessing prototype!`
}
for (const key in friends) {
if (Object.hasOwn(friends, key)) {
console.log(key); // 'friend1', 'friend2'
}else {
console.log(key); // makeNewFriends
}
}
정확성, 안전성, 예측성의 관점에서 클래스는 대체로 타입과 비슷하므로 동적으로 프로퍼티를 변경할 수 있는 프로토타입에 비해 더 신뢰성 높은 코드를 작성할 수 있다고 하는 게 주요 의견이다.
"글이 길어진 관계로... 다음 챕터에서..."
재밌어서 시간 가는줄 모르고 학습했다. 배워도 끝이 없다...ㅋㅋㅋㅋ 아직 프로토타입과 클래스 차이점을 두고, 자바스크립트에서 어떻게 상속을 하는 것이 효율적인지 판단이 서지를 않는다.