[JS] Prototype with Chaining

colki·2021년 5월 8일
0
post-custom-banner

Udemy_JavaScript: The Advanced JavaScript Concepts (2021) 강의를 바탕으로 메모한 내용입니다.

Colki Velog_프로토타입 이해 포스팅으로 공부했던 개념이지만 낯설다 너.. 다시 공부하는 프로토타입! gogo 😳



Constructor & Prototype

의식의 흐름에 따라 주관적인 비유에 덧대어 작성한 내용이라 틀렸을 수 있습니다 :)
유데미에서 이렇게 강의한 부분은 없습니다.

자바스크립트 세상에서 함수는 자식을 만들 수 있는 엄마같은 존재이다.
자식을 가지는 이런 함수를 생성자함수라고 한다. 영어로는 Constructor.

생성자엄마함수는 객체이기 때문에 속성을 가지고 있다.

속성 중에 prototype이라는 애가 있는데, 이 속성은 함수가 자식을 가지기로 한 순간 부터,
예를 들면 new 키워드와 함께 나는 생성자엄마함수가 될 함수야! 하고 외치는 순간
모든 함수가 가지고 있던 prototype 속성이 잠을 자고 있다가 깨어난다! 봉인해제!

(생성자 함수가 아닌 일반 함수가 가지고 있는 prototype은 신경 안써도 됨)

잠에서 깨어난 이 속성은 쉽게 생각해서 탈부착? 되는 놈이다.
그래서 생성자엄마함수가 가지고 있지만 밖에서 혼자 따로 다니는 객체이기도 하다.
그리고 놀랍게도 prototype은 Constructor의 도플갱어이다.👨🏿‍🤝‍👨🏿

프로토타입 체인구조에서 Function, Array, Object객체를 부를 때는 Function.prototype, Array.prototype, Object.prototype이라고 부른다.
이름만 부르는게 아니라 성까지 붙여서 부르는 늬낌?

😶 생성자함수만 가지고 있는 것이 prototype이니,
Object.prototype이라 하면 이때의 Object는 무조건 생성자엄마함수가 된다.
엄마의 Full Name인 것이다.
Object.prototype.__proto__는 생성자함수의 엄마 좀 가리켜봐 라고 하는 것.
생성자함수의 어머니는 Function 이므로 Function의 full name Function.prototype과 같다.
(proto에 대한 이야기는 아래에서 자세히 다룰 것이다!)


떼어놓고 보자면 아래 그림과 같다.
너는 나. 나는 너.



__proto__ & Prototype

proto란 무엇이냐. 바로 생성자엄마함수가 만든 자식들만이 가지고 있는 속성이다.

자식.__proto__ 은 엄마의 풀네임엄마.prototype을 가리킨다.

proto points to the prototype up to chain.

동일시해서 생각하면 엄청 헷갈려진다. '가리킨다' 'point '의 의미로 생각하는 게 이해에 쉽다. (link의 개념)

😁 자식.__proto__ 은 난 누군가의 자식이다 라는 뜻이다.
예를 들어서 Object.__proto__ 에서의 Object는 인스턴스이다.

😁 인스턴스인데 앞 철자가 대문자이니 생성자함수이고, 그런 함수의 엄마는
생성자함수들의 어머니 역시, 생성자함수인 Function 이다.

object and arrays and functions in JavaScript are objects they inherit through the prototype chain from the base object.

그리고 proto 의 끝, 가장 상위에는 바로 바로 객체가 있다.
모계유전인 자바스크립트 사회에서 빅마마, 최종빌런, 조물주 는 Object 이다.

💡 생성자함수들의 어머니는 Function.prototype.

Array.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototype

💡 Array.prototype, Function.prototype이 모시는
빅마마, 최종빌런 , 조물주는 Object.prototype이다.

Array.prototype.__proto__ === Object.prototype
Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null // 조물주의 엄마는 없다

대문자라서 헷갈릴 때는 객체 이름 뒤에 __proto__가 오는지 prototype이 오는지 확인하면 된다.


  • Exercise 01
function foo() {} // 함수 인스턴스 foo
foo.__proto__; // f() {[native code]}
Function.prototype; // f() {[native code]}

여기서 foo.__proto__엄마.prototype을 가리켜야 하므로
자기를 낳은 생성자함수 Function.prototype을 가리킨다.


  • Exercise 02
const array = []; // 배열 인스턴스 array
array.hasOwnProperty('map'); // false
array.__proto__.hasOwnProperty('map'); // true
array.__proto__ === Array.prototype; // true 

array라는 자식을 만들었다. 이 array는 map 메서드를 직접 가지고 있지 않다.
그러므로 자신의 엄마인 Array에게서 빌려와서 사용한다.
hasOwnProperty는 너가 직접 가지고 있는 거니? 물어보는 메서드이므로 대답은 false가 된다.

여기서 array.__proto__엄마.prototype을 가리켜야 하므로
자기를 낳은 생성자함수 Array.prototype을 가리킨다.


  • Exercise 03

Object.__proto__ === Function.prototype // true

Function.prototype.__proto__=== Object.prototype // true
Object.__proto__.__proto__=== Object.prototype // true
  
Object.prototype.__proto__ // null 

Objet.__proto__ 라고 하면 이때의 Object는 인스턴스라고 규정하는 것이기 때문에

이때의 Object는 최종빌런, 조물주 객체가 아니라 그저 Function의 인스턴스로써의 생성자함수인 Object인 것이다.

반면에 Object.prototype이라 하면 최종빌런 Object 객체를 가리키는 것이다.

use 'proto' is bad performance

이렇게 열심히 공부했지만, 사실 실제로는 __proto__는 사용하지 않는 속성이다. 개념이해를 위해 필요한 존재이지만 실제로는 문제를 일으킬 수 있는 소지가 있어서 쓰지 않는다고 한다.


Prototype Chain

함수인스턴스 foo >> 생성자함수 Function >> 최종빌런 Object 를 예로 들어서 프로토타입 체인을 나타낸 그림이다.

😁 근래 만든 것 중 맘에 든닷! 체인 잘 만든 것 같다.헿 😁

함수 챕터에서 공부했던 대로, 기본적으로 우리가 만든 foo함수에게는 다음과 같은 속성이 있다.
code(), name, 기타 properties , + __proto__

여기서 기타 properties는 foo함수가 다이렉트로 가지고 있는 속성이다. properties === hasOwnProperty

hasOwnProperty( ) 로 이 속성 진짜 니꺼냐?라고 물어봤을 때 true를 뱉는 속성들이 여기에 해당된다.

function foo() {}
  
foo.weight = `100kg`;
foo.hasOwnProperty('weight'); // true

foo.call();
foo.hasOwnProperty('call'); // false

그런데 foo 함수는 hasOwnProperty 메서드로 확인하면 false가 나오는, 우리가 따로 할당한 적이 없는 call, bind, apply 등의 메서드도 사용할 수 있다.

이걸 사용할 수 있게 하는 것이 그림속의 chain 덕분이다.
foo.__proto__Function.prototype을 가리키는 덕분에
Function 객체의 속성을 끌어다 쓸 수 있는 것이다.

여기서 Prototype Chain은 단순히 가리키는 목적을 위해서만 존재하는 것이 아니라 연결된 LINK의 개념이라 봐도 무방하다.
또한 상위 객체에게서 상속받는다고 표현할 수도 있다.

사실 자바스크립트 언어에서 상속은 없다고 하는데 
표면적으로 유사하기 때문에 일반적으로 많이 쓰는 표현이다.

Object.create()

proto 는 실제로 사용하면 안되는 속성이기 때문에 다른 방법으로 프로토타입체인에서 프로퍼티를 상속받게 해야 한다.

그 방법 중 하나가 Object.create() 메서드이다.


▪ ( ) 안의 객체의 프로토타입 속성을 갖는 새로운 객체를 만든다.

const 상속받을 빈 객체 = Object.create(부모 객체)

빈 객체 생성 후 프로퍼티 추가

let human = {
  mortal: true
}

let socrates = Object.create(human);

console.log(socrates); // {}
console.log(human.isPrototypeOf(socrates)); // true

socrates.age = 40;
console.log(socrates); // {age: 40}

console.log(socrates.mortal); // true

socrates.mortal = false;
console.log(socrates.mortal); // false

▪ class간의 상속을 구현할 때도 사용한다.

💡 클래스(상위-하위계층)간의 상속.

하위.prototype = Object.create(상위.prototype)
하위.prototype.constructor = 하위

Object.create() 로 만들어지는 (빈)객체(하위.prototype)에 상위.prototype로 재할당된다.
재할당 전에 하위.prototype에 부여한 속성은 재할당 이후 사라지게 된다.
그래서 재할당 이전의 객체의 생성자함수가 누군지 다시 명시를 해줘야 한다.

function Animal () {
  this.eat = function () { console.log("EAT!"); };
}

Animal.prototype.sleep = function () { console.log("sleep"); };

function Human (name) {
  Animal.call(this);
  this.name = name;
}

Human.prototype = Object.create(Animal.prototype);
Human.prototype.constructor = Human;

var colki = new Human("colki ");
var dog = new Animal();

dog.sleep(); // sleep
colki .sleep(); // sleep

새 객체는 Animal.prototype 객체를 자신의 프로토타입으로 가지고 있으므로 Animal.prototype에 정의된 모든 메소드를 사용할 수 있게 된다.

하지만 이렇게 해버리면 Human.prototype.constructor 또한 Animal이 된다.
이건 자연의 섭리에 한 참 어긋나는 일이므로 이부분도 고쳐주어야 마땅하다.

💡 undefined & TypeError

JavaScript engine doesn't find anything up the prototype chain.
체인에서 못 찾을 때 만나는 2가지 에러 유형

  • 없는 메서드(프로퍼티)를 참조할 때
    ~.property => undefined

  • 없는 메서드(프로퍼티)를 실행할때
    ~.property(); => TypeError


💡 Property prototype

every function has a prototype property

The only time we really use prototypes is using what we call constructor functions constructor functions.

prototype은 모든 함수가 가지고 있는 속성이지만, 함수가 생성자함수로써 작동할 때의 prototype만 특별한 기능을 하게 된다고 글 도입부에서 얘기했었다.

최종빌런 Object의 type을 확인해보면,

typeof Object === 'function' 으로 함수라고 나온다.

왜냐하면 Object.가 prototype이란 속성을 가졌기 때문에 Object는 함수 객체인 것이다.

protyotype을 갖는 Object.prototype은 생성자 엄마함수니까!

반면에 함수 Object가 아닌 인스턴스 객체의 타입을 찍으면 object가 나온다.

typeof {} === 'object'


const obj = {};
typeof obj === 'object'; // true

obj.prototype; 
// undefined
// It's not a function


const array = [];
array.prototype; 
// undefined
// It's not a function

'string'.__proto__ === String.prototype 
'string'.prototype;
// undefined
// It's a Primary Type
  

💡 Benefit of Prototype

The fact that objects can share prototypes means that you can have objects with properties that are pointing to the same place in memory thus being more efficient.

We're not repeating ourselves and we're saving ourselves memory

부모 객체의 프로퍼티를 상속받게 된다는 것은 부모 객체의 레퍼런스를 그대로 쓰면 되는 것을 말한다. same place in memory
즉 자식이 갖다 쓰고자 하는 프로퍼티를 더블로 메모리에 저장할 필요가 없어지게 되므로 메모리 공간을 효율적으로 사용할 수 있다.

또한 우리는 반복적으로 코드를 쓰지 않아도 되니 dry한 코딩을 할 수 있다.


💡 Excercise

exercise 01

Date object => to have new method .lastYear( ) which shows you last year
'YYYY' format. new Date가 가지고 있지 않은 lastYear 메서드를 사용할 수 있게 만들어서 콘솔에 지난해를 출력해라.

  new Date('1900-10-10').lastYear();  
  
  // '1899' 가 출력돼야 함.

exercise 02

How can we modify the map method that we have on a race to print a actual map emoji'💨' at the end of each item. not give it anything to map()

Can we modify what these built in methods by javascript do.
인자를 아무것도 주지 않고 map 메서드를 이용해서
배열의 각 요소 끝에 이모지를 추가해라.

console.log([1, 2, 3].map()); 
  
// ["1💨", "2💨", "3💨"] 이 출력돼야 함.
  
  

exercise 03

How would you be able to create your own .bind() method using call or apply.

Hint:
Function.prototype.bind = function(){
}

exercise answer

exercise 01

Date.prototype.lastYear = function () {
  return this.getFullYear() - 1;
}
  
console.log(new Date('1900-10-10').lastYear()); // 1899

화살표함수를 쓰면 this를 객체에 바인딩하지 못하므로 사용하면 안된다.

exercise 02

We want to loop over whatever we get and remember to the left of the dot. => this

this[i] + '💨' this is type caution in JavaScript.
This is going to be turned well into a string for us.
And because this is an expression we're going to wrap this in round brackets. (this[i] + '💨')

소괄호로 한 번더 감싸지 않아도 나오긴 하는데 느리다!
Array.prototype.map = function () {
  let arr = []

  for (let i = 0; i < this.length; i++) {
    arr.push((this[i] + '💨')); 
  }
                                  
  return arr;
}

console.log([1, 2, 3].map()); 
// ["1💨", "2💨", "3💨"]
  

If you go look at how map is implemented it returns a new array like this.
We don't modify the existing array.

lodash 에서 map을 구현한 것과 같은 고차함수 원리이다. 인자를 바꾸면 안됨!

What if somebody just modifies the actual functionality of map and all the code in our project that

uses map is now well destroyed.

you should never really modify the existing methods that we have

이제 위 로직 이후로는 기존의 map메서드는 파괴되고 우리가 새로 만든 map이 사용될 것이다. 그래서 기존 메서드는 수정하면 안됨!

exercise 03

Function.prototype.bind = function(whoIsCallingMe){
  const self = this;

  return function(...args){
    return self.apply(whoIsCallingMe, args);
  };
}

function foo(a, b, c) {
  return `${this.name} is ${(a * b * c)}`
}

let colki = { name: 'hany'};

const result = foo.bind(colki);
console.log(result(2, 3, 4)); // hany is 24


it's a little bit old school.

There's newer cleaner way of doing this using things like the class keyword

안드레 니고이가 덧붙인 말에 따르면, prototype을 이용한 상속은 구식이라 이제 잘 안 쓴다고 한다..

대신 Class를 이용해서 simple하게 inheritance를 할 수 있다고 함!
산넘어 산이로세 😲

profile
매일 성장하는 프론트엔드 개발자
post-custom-banner

0개의 댓글