자바스크립트 프로토타입 prototype? hasOwnProperty? Class?

Devfrank·2023년 1월 26일
1

JavaScript

목록 보기
1/3
post-thumbnail

자바스크립트는 10일만에 만들어진 프로토타입 기반 객체지향 프로그래밍 언어다. 자바스크립트에서 프로토타입이 얼마나 강력한지 알기 위해 프로토타입 개념을 더 자세히 학습하고자 한다. 감사하게도 정리가 잘 되어 있는 온라인 소스가 많아서 부분부분 참고하며 글로 풀어보려고 한다.
목표: 나 자신도 프로토타입 개념에 자신감을 가지고, 이 글을 보는 모든 사람이 이해할 수 있도록 simple Korean으로 풀어 설명하는 것이다.

객체지향이란? (관계/조직에 초점)

그러면 객체지향 프로그래밍 언어란 뭘까? 객체지향 절차지향 등 어려운 용어가 상당히 많다. Simple Korean으로 풀어보면...

  • 우리가 실생활에서 쓰는 모든 것, 상태와 행위를 객체라 한다. 이 객체들을 레고 블럭처럼 조립해서 하나의 프로그램을 만드는 것을 객체 지향 프로그래밍이라고 할 수 있다.

객체지향을 들으면서, 절자 지향 프로그래밍도 들어봤는데 차이점이 뭐지?

절차지향은 순차적으로 실행에 초점이 되어있고
객체지향은 관계/조직에 초점을 맞추고 있다는 초점의 차이일 뿐이다.
절차적 프로그래밍이라고 해서 객체를 다루지 않는 것이 아니고, 객체지향 프로그래밍이라고해서 절차가 없는 것도 아니다.

프로토타입이란 (prototype)?

프로토타입 정의를 알아보기 전에, 그나마? 친근한 용어를 이용해 특징을 알아보는 것도 좋을 것 같다.

  • 자바스크립트에서 단순 원시 타입(simple primitive)인 문자열, 숫자, 불리언, null, undefined를 제외한 모든 타입은 객체다. 그렇다. 자바스크립트에서는 배열, 객체, 함수도 객체다.

  • JavaScript의 모든 객체(배열, 객체, 함수 등)는 상위 객체를 참조하며, 이 상위 객체를 프로토타입이라고 한다. 일단 상위 객체를 참조한다는 말만 기억해두자.

프로토타입 Prototype 정의

위키디피아 정의 - 프로토타입 기반 언어는 클래스 기반 언어에서 상속을 사용하는 것과는 다르게, 객체를 원형(프로토타입)으로 하는 복제 과정을 통해 객체의 동작 방식을 재사용 할 수 있게 한다.

프로토타입 기반 언어는 원형(원본) 객체를 복제하여 새로운 객체를 하는 생성하는 언어를 말한다. 그런데 자바스크립트도 복제를 할까? 자바스크립트는 약간 다르다. 복제가 아닌 프로토타입 링크를 통해 원형(원본)을 참조한다.

  • 프로토타입 링크를 통해 -> 원형을 참조한다를 꼭!!!!!!!! 기억하자.
  • 결국 객체지향 언어다. 이는, 관계와 조직에 초점을 둔다는 점도 기억하자.

적절한 비유일지는 모르겠지만...... 내가 이해한 프로토타입은 리액트와 어렴풋이 비슷하다.

  1. 리액트로 생각해보면, 부모와 자식사이에서 props나 함수를 공유한다.
    -----> 프로토타입도 부모 자식 사이의 공유와 비슷하다.

  2. 리액트에서도 부모 컴포넌트가 최상위고, 자식 컴포넌트들은 부모가 던져준 props나 함수를 사용할 수 있다.
    ----->자식은 부모가 공유해주는 prototype 함수를 사용할 수 있다.

  3. props drilling와 비슷하게 props를 위 아래로 넘겨줄 수 있는 체인 매커니즘을
    -----> 프로토타입 체인이라고 부른다.

  4. 리액트에서 map을 사용할 때 key 값이 필요하듯, 부모와 자식의 연결고리 key를
    -----> _proto_ 라고 부른다. _proto_를 이용하여 상위를 참조한다.

예제 1

  • 위에서 언급한 부모와 자식 사이의 공유를 염두해두자.
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());
  • 그러면!!! 우리는 자식과의 공유를 원한다. 만약 내가 add 함수에 다른 함수를 추가하고 싶다면?
  • prototype에 추가하는 것과 일반 함수에 추가하는 것의 차이점이 뭘까?
  • 어떻게 사용하면 다른 환경에서 공유를 할 수 있을까?

일반 메소드 vs Prototype

  1. prototype에 추가하는 것과 일반 함수에 추가하는 것의 차이점이 뭘까?
  2. 어떻게 사용하면 다른 환경에서 공유를 할 수 있을까?

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의 특성 프로토타입 체인(체인 메커니즘)을 통해 상위를 참조해서 함수가 사용 가능해졌다.

프로토타입 기반이라는 의미는 위에서 언급한 것처럼, 상위 객체를 참조하며, 이 상위 객체를 프로토타입이라고 한다.

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: ƒ}

프로토타입 Prototype 특징 정리

  • JavaScript의 모든 객체의 프로토타입은 값을 할당하는 시점에 결정되며, 프로토타입은 변경될 수 있다.
  • JavaScript는 배열, 문자열 심지어 함수도 객체로 간주한다.
  • 프로토타입은 상위 객체를 의미한다.
  • 직접 생성한 프로토타입은 새로운 프로퍼티나 메소드를 마음껏 추가하거나 삭제할 수 있다.
  • 물론 자바스크립트 표준 객체의 프로토타입도 임의로 수정할 수 있으나, 심각한 오류가 발생할 가능성이 있다. 따라서 자바스크립트 표준 객체의 프로토타입은 수정해서는 안된다.
  • 객체는 함수를 사용해서 만들어지고, 객체는 함수의 프로토타입 객체를 복제하여 생성된다.
  • 모든 객체는 자신이 어떤 원본 객체를 복제하여 생성된 것인지에 대한 정보를 가지고 있다.
  • 프로토타입에 메소드를 추가하면 해당 생성자로 생성된 모든 객체에서 사용 가능하다.
  • 객체를 생성하면 생성한 만큼 함수가 등록된다. 그리고 함수를 여러 개 등록하면 메모리 공간을 많이 차지해 메모리 낭비가 된다. 프로토타입을 사용함으로 메모리 낭비를 줄일 수 있다.

hasOwnProperty vs Prototype

이 메소드는 객체가 특정 프로퍼티를 자기만의 직접적인 프로퍼티로서 소유하고 있는지를 판단하는데 사용된다. in 연산과는 다르게, 이 메소드는 객체의 프로토타입 체인을 확인하지는 않는다.

  • 해당 객체에 특정 프로퍼티가 존재하면 true, 그렇지 않으면 false 를 반환한다.
  • 프로토타입 체인 따위는 신경쓰지 않는 돌직구 녀석.
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
  }
}

클래스 vs Prototype

정확성, 안전성, 예측성의 관점에서 클래스는 대체로 타입과 비슷하므로 동적으로 프로퍼티를 변경할 수 있는 프로토타입에 비해 더 신뢰성 높은 코드를 작성할 수 있다고 하는 게 주요 의견이다.

"글이 길어진 관계로... 다음 챕터에서..."

마무리

재밌어서 시간 가는줄 모르고 학습했다. 배워도 끝이 없다...ㅋㅋㅋㅋ 아직 프로토타입과 클래스 차이점을 두고, 자바스크립트에서 어떻게 상속을 하는 것이 효율적인지 판단이 서지를 않는다.

Reference

profile
피드백 문화를 지향합니다. Anytime please! 🙌🏻

0개의 댓글