분류 Nodejs

JavaScript - 프로토 타입 체인 Depth

컨텐츠 정보

  • 조회 340 (작성일 )

본문

프로토 타입 체인을 통한 상속 개념 배우기 


이 기사에서는 JavaScript 프로토 타입 체인에 대해 학습합니다. 객체가 다른 객체에 연결되는 방법과 "상속"및 이러한 객체 간의 관계를 구현하는 방법을 살펴 보겠습니다.


https://dev.to/sag1v/javascript-the-prototype-chain-in-depth-2p58 


우리의 목표 


개발자로서 코드를 작성할 때 우리의 주요 임무는 일반적으로 데이터를 조작하는 것입니다. 데이터를 가져 와서 어느 곳에 저장 한 다음 해당 데이터에서 기능을 실행합니다.


기능과 관련 데이터를 같은 장소에 묶어 두는 것이 좋지 않습니까? 이것은 우리에게 훨씬 쉬워 질 것입니다.


Player 객체를 상상해보십시오.


{
  userName: 'sag1v',
  score: '700'
}


점수 변경과 같이 해당 개체에서 기능을 실행하려면 어떻게 해야 합니까? setScore 메소드를 어디에 두겠습니까?


Objects 


관련 데이터를 저장하려면 일반적으로 객체를 사용하고 상자처럼 사용하며 관련 조각을 넣습니다. 시작하기 전에 먼저 객체가 무엇인지 이해하고 객체를 생성 할 수 있는 몇 가지 방법을 살펴 보겠습니다.


객체 리터럴 


const player1 = {
  userName: 'sag1v',
  score: '700',
  setScore(newScore){
    player1.score = newScore;
  }
}


리터럴 표기법 (또는 "개체 이니셜 라이저")이 있는 개체는 표현식이며 각 개체 이니셜 라이저는 나타나는 문이 실행될 때마다 새 개체가 만들어집니다.


점 표기법 또는 대괄호 표기법으로 객체의 속성을 만들거나 액세스 할 수도 있습니다.


const player1 = {
  name: 'Sagiv',
}

player1.userName = 'sag1v';
player1['score'] = 700;
player1.setScore = function(newScore) {
  player1.score = newScore;
}


Object.create 


객체를 생성하는 또 다른 옵션은 Object.create 메소드를 사용하는 것입니다.


const player1 = Object.create(null)
player1.userName = 'sag1v';
player1['score'] = 700;
player1.setScore = function(newScore) {
  player1.score = newScore;
}


Object.create는 항상 비어있는 새 객체를 반환하지만 다른 객체를 전달하면 보너스 기능이 제공됩니다. 나중에 다시 설명하겠습니다.


자동화 


분명히 우리는 매번 이러한 객체를 직접 만들고 싶지 않을 것입니다.이 작업을 자동화하고 싶을 수도 있습니다. 

우리를 위해 Player 객체를 생성하는 함수를 만들어 봅시다.


Factory Functions 


function createPlayer(userName, score) {
  const newPlayer = {
    userName,
    score,
    setScore(newScore) {
      newPlayer.score = newScore;
    }
  }
  return newPlayer;
}

const player1 = createPlayer('sag1v', 700);


이 패턴은 일반적으로 "공장 함수"이라고 하며, 물건을 출력하는 공장의 컨베이어 벨트와 같이, 관련 인수를 전달하고 필요한 물건을 다시 가져옵니다.


이 함수을 두 번 실행하면 어떻게 됩니까?


function createPlayer(userName, score) {
  const newPlayer = {
    userName,
    score,
    setScore(newScore) {
      newPlayer.score = newScore;
    }
  }
  return newPlayer;
}

const player1 = createPlayer('sag1v', 700);
const player2 = createPlayer('sarah', 900);


이 모양을 가진 2 개의 객체를 얻게 됩니다 :


{
  userName: 'sag1v',
  score: 700,
  setScore: ƒ
}

{
  userName: 'sarah',
  score: 900,
  setScore: ƒ
}


중복 된 부분이 있습니까? 우리의 setScore는 각 인스턴스에 저장되며, D.R.Y (Do n't Repeat Yourself) 원칙을 어 기고 있습니다.


다른 곳에 한 번 저장할 수 있고 여전히 객체 인스턴스 player1.setScore (1000)를 통해 액세스 할 수 있다면 어떨까요?


OLOO - Objects Linked To Other Objects 


Object.create로 돌아와서 항상 빈 객체를 만들 것이라고 말했지만 객체를 전달하면 보너스 기능을 얻습니다.


const playerFunctions = {
  setScore(newScore) {
    this.score = newScore;
  }
}

function createPlayer(userName, score) {
  const newPlayer = Object.create(playerFunctions);
  newPlayer.userName = userName;
  newPlayer.score = score;
  return newPlayer;
}

const player1 = createPlayer('sag1v', 700);
const player2 = createPlayer('sarah', 900);

이 코드는 하나의 중요한 차이점을 제외하고는 이전 코드와 똑같이 작동합니다. 새 객체 인스턴스는 setScore 메소드를 보유하지 않으며 playerFunctions에 링크되어 있습니다.


자바 스크립트의 모든 객체에는 __proto __ ( "dunder proto"로 발음)이라는 특수한 숨겨진 속성이 있으며 해당 속성이 객체를 가리키는 경우 엔진은 이 객체의 속성을 마치 인스턴스 자체에 있는 것처럼 처리합니다. 즉, 모든 객체는 __proto__ 속성을 통해 다른 객체에 연결하여 자신의 속성에 액세스 할 수 있습니다.


⚠️ 참고 


__proto__를 prototype 속성과 혼동하지 마십시오. prototype은 함수에만 존재하는 속성입니다. 반면에 __proto__는 객체에만 존재하는 속성입니다. 좀 더 혼란을 주기 위해 __proto__ 속성을 EcmaScript 사양에서 [[Prototype]]이라고 합니다.


우리는 나중에 ?으로 돌아올 것입니다.


더 나은 시각화를 위해 코드가 포함 된 예를 살펴 보겠습니다.


const playerFunctions = {
  setScore(newScore) {
    this.score = newScore;
  }
}

function createPlayer(userName, score) {
  const newPlayer = Object.create(playerFunctions);
  newPlayer.userName = userName;
  newPlayer.score = score;
  return newPlayer;
}

const player1 = createPlayer('sag1v', 700);
const player2 = createPlayer('sarah', 900);

console.log(player1)
console.log(player2)


출력됩니다 :


player1: {
  userName: 'sag1v',
  score: 700,
  __proto__: playerFunctions
}

player2: {
  userName: 'sarah',
  score: 900,
  __proto__: playerFunctions
}

즉, player1과 player2는 playerFunctions의 속성에 액세스 할 수 있습니다. 즉, 둘 다 setScore를 실행할 수 있습니다.


player1.setScore(1000);
player2.setScore(2000);


우리는 여기서 목표를 달성했으며 데이터와 기능이 첨부 된 객체를 가지고 있었으며 D.R.Y 원칙을 어 기지 않았습니다.


그러나 이것은 연결된 객체를 만들기 위해 많은 노력을 기울이는 것 같습니다.

  1. 객체를 만들어야 합니다.
  2. 기능을 보유한 다른 객체를 만들어야 합니다.
  3. __proto__ 속성을 기능성 개체에 연결하려면 Object.create를 사용해야 합니다.
  4. 새 객체를 속성으로 채워야 합니다.
  5. 새 객체를 반환해야 합니다.

이러한 작업 중 일부를 수행 할 수 있으면 어떻게 됩니까?


새로운 연산자-A.K.A 생성자 함수 


이전 예제에서 우리는 팩토리 함수 내부에 연결된 객체를 만들기 위해 해야 ​​할 "작업"이 있음을 보았다. 함수 호출과 함께 새로운 연산자를 사용하면 JavaScript가 이러한 작업 중 일부를 수행 할 수 있습니다.


그러나 실제로 작동하기 전에 함수가 무엇인지에 대해 같은 페이지에 있는지 확인하십시오.


실제로 함수란 무엇입니까? 


function double(num) {
    return num * 2;
}

double.someProp = 'Hi there!';

double(5); // 10
double.someProp // Hi there!

double.prototype // {}

우리 모두 함수가 옳다는 것을 알고 있습니까? 선언 한 다음 괄호 ()로 호출 할 수 있습니다. 그러나 위의 코드를 살펴보면 객체와 마찬가지로 속성을 읽거나 작성할 수 있습니다. 여기에서 제 결론은 JavaScript의 함수는 단순한 함수가 아니라 일종의 "함수와 객체 조합"입니다. 기본적으로 모든 함수를 호출하고 객체처럼 취급 할 수 있습니다.


프로토 타입 속성


모든 함수 (화살표 함수 제외)에는 .prototype 속성이 있습니다.


예, 여기 다시 경고가 있습니다.


Not __proto__ or [[Prototype]], but prototype. 



이제 새 연산자로 돌아갑니다.


새로운 연산자로 호출


이것은 새로운 연산자에서 우리 함수가 어떻게 생겼는지 보여줍니다 :


⚠️이 키워드의 작동 방식을 100 % 확신 할 수 없다면 JavaScript- "this"키워드를 읽어보십시오.


function Player(userName, score){
  this.userName = userName;
  this.score = score;
}

Player.prototype.setScore = function(newScore){
  this.score = newScore;
}

const player1 = new Player('sag1v', 700);
const player2 = new Player('sarah', 900);

console.log(player1)
console.log(player2)


그리고 이것은 출력입니다 :


Player {
  userName: "sag1v",
  score: 700,
  __proto__: Player.prototype
}

Player {
  userName: "sarah",
  score: 900,
  __proto__: Player.prototype
}


그 코드를 살펴 보자 (실행 단계)


우리는 new 연산자를 사용하여 Player 함수를 실행하고 있습니다. 함수 이름은 createPlayer에서 Player로 변경되었습니다. 개발자 간의 규칙이기 때문입니다. 이것은 Player 함수의 소비자에게 이것이 "Constructor Function"이고 새로운 연산자로 호출되어야 한다는 것을 신호하는 방법입니다.


new 연산자로 함수를 호출하면 JavaScript는 다음과 같은 4 가지 작업을 수행합니다.


  1. 새로운 객체를 생성합니다.
  2. 새로운 컨텍스트를 this 컨텍스트에 할당합니다.
  3. 새 객체의 __proto__ 속성을 함수의 prototype 속성에 연결합니다. 우리의 경우 Player.prototype.
  4. 다른 객체를 반환하지 않으면 this 새로운 객체를 반환합니다.

JavaScript로 자동 단계를 작성하면 다음과 같은 코드 조각처럼 보일 수 있습니다.

function Player(userName, score){
  this = {} // ⚠️ done by JavaScript
  this.__proto__ = Player.prototype // ⚠️ done by JavaScript

  this.userName = userName;
  this.score = score;

  return this // ⚠️ done by JavaScript
}


3 단계를 보자.


It will linked the __proto__ property of that new object to the prototype property of the function... 


즉, Player.prototype에 메소드를 넣을 수 있으며 새로 만든 객체에서 사용할 수 있습니다.


이것이 바로 우리가 한 일입니다.

Player.prototype.setScore = function(newScore){
  this.score = newScore;
}


생성자 함수를 사용하여 다른 객체에 연결된 객체를 만드는 방법입니다.


그런데 새로운 연산자를 사용하지 않으면 JavaScript는 이러한 작업을 수행하지 않고 이 컨텍스트에서 일부 속성을 변경하거나 생성하게 됩니다. 이 옵션을 기억하십시오. 서브 클래싱을 할 때 이 트릭을 사용합니다.


새 연산자로 함수가 호출되었는지 확인하는 방법이 있습니다.


function Player(username, score){

  if(!(this instanceof Player)){
    throw new Error('Player must be called with new')
  }

  // ES2015 syntax
  if(!new.target){
    throw new Error('Player must be called with new')
  }
}

이 핵심 단어에 대한 자세한 설명은 JavaScript- "this"핵심 단어를 읽을 수 있습니다.


Class 


직접 팩토리 함수를 작성하지 않거나 생성자 함수 구문이 마음에 들지 않거나 새 연산자로 함수가 호출되었는지 수동으로 확인하는 경우 JavaScript는 ES2015 이후 클래스도 제공합니다. 클래스는 대부분 함수에 대한 구문 설탕이며 다른 언어의 전통적인 클래스와는 매우 다르지만 여전히 "prototypal inheritance"를 사용하고 있습니다.


MDN의 인용문 :


ECMAScript 2015에 도입 된 JavaScript 클래스는 주로 JavaScript의 기존 프로토 타입 기반 상속에 비해 구문상의 설탕입니다. 클래스 구문은 JavaScript에 새로운 객체 지향 상속 모델을 도입하지 않습니다.


"생성자 함수"를 단계별로 클래스로 변환하자 :


클래스 선언 


우리는 클래스 키워드를 사용하고 이전 섹션에서 생성자 함수를 명명 한 것과 같은 방식으로 클래스 이름을 지정합니다.


class Player {

}


생성자 생성 


이전 섹션에서 생성자 함수의 본문을 가져 와서 클래스에 대한 생성자 메서드를 만듭니다.


class Player {
  constructor(userName, score) {
    this.userName = userName;
    this.score = score;
  }
}

클래스에 메소드 추가 


Player.prototype에 첨부하려는 모든 메소드는 단순히 클래스 메소드로 선언 할 수 있습니다.


class Player {
  constructor(userName, score) {
    this.userName = userName;
    this.score = score;
  }

  setScore(newScore) {
    this.score = newScore;
  }
}

이제 전체 코드


class Player {
  constructor(userName, score) {
    this.userName = userName;
    this.score = score;
  }

  setScore(newScore) {
    this.score = newScore;
  }
}

const player1 = new Player('sag1v', 700);
const player2 = new Player('sarah', 900);

console.log(player1)
console.log(player2)


코드를 실행할 때 이전과 동일한 출력을 얻습니다.


Player {
  userName: "sag1v",
  score: 700,
  __proto__: Player.prototype
}

Player {
  userName: "sarah",
  score: 900,
  __proto__: Player.prototype
}

보시다시피, 클래스는 작동하고 프로토 타입 체인을 가진 함수와 동일하게 동작합니다. 또한 클래스 연산자가 new 연산자로 호출되었는지 확인하는 함수도 내장되어 있습니다.


하위 분류-A.K.A 상속 


사용자 이름을 변경하는 기능과 같이 일반 플레이어가 가지고 있지 않은 기능을 잠금 해제 한 유료 사용자 플레이어 인 특수 플레이어를 원한다면 어떻게 해야 합니까?


우리의 목표가 무엇인지 봅시다 :

  • 우리는 일반 플레이어가 userName, 점수 및 setScore 메소드를 갖기를 원합니다.
  • 또한 일반 플레이어가 가진 모든 것 + setUserName 메소드를 갖는 유료 사용자 플레이어를 원하지만 분명히 일반 플레이어 가 이 기능을 갖기를 원하지 않습니다.

자세히 살펴보기 전에 연결된 객체 체인을 시각화 하십시오.


아래 코드를 고려하십시오.


function double(num){
    return num * 2;
}

double.toString() // where is this method coming from?

Function.prototype // {toString: f, call: f, bind: f}

double.hasOwnProperty('name') // where is this method coming from?

Function.prototype.__proto__ // -> Object.prototype {hasOwnProperty: f}

속성이 객체에 직접 있지 않은 경우 엔진은 __proto__ 속성을 통해 연결된 객체 (있는 경우)에서이 속성을 찾습니다. 

그러나 우리가 찾고 있는 재산이 없다면 어떻게 될까요? 이전에 배운 것처럼 모든 객체에는 __proto__ 속성이 있으므로 엔진은 __proto__ 속성을 통해 다음에 연결된 객체를 확인합니다. 

찾고 있는 속성이 없는 경우? 글쎄, 당신이 그것을 얻는다고 생각하면, 엔진은 막 다른 골목에 도달 할 때까지 __proto__ 속성을 통해 체인을 계속 올라갈 것입니다. 예를 들어 null 참조는 기본적으로 Object.prototype .__ proto__입니다.


코드 예제를 살펴보면 다음과 같습니다.


double.toString()


  1. double에는 toString 메소드가 없습니다 ✖️.
  2. double .__ proto__를 통해 가십시오.
  3. double .__ proto__는 toString 메소드를 포함하는 객체 인 Function.prototype을 가리 킵니다. 확인 ✔️
double.hasOwnProperty('name')
  1. double에는 hasOwnProperty 메소드가 없습니다 ✖️.
  2. double .__ proto__를 통해 가십시오.
  3. double .__ proto__가 Function.prototype을 가리 킵니다.
  4. Function.prototype에는 hasOwnProperty 메소드가 없습니다 ✖️.
  5. Function.prototype .__ proto__을 통해 이동하십시오.
  6. Function.prototype .__ proto__가 Object.prototype을 가리키고 있습니다.
  7. Object.prototype은 hasOwnProperty 메소드를 포함하는 오브젝트입니다. 확인 ✔️

다음은 프로세스를 보여주는 작은 애니메이션 GIF입니다.


Showing the steps of the prototype chain 


이제 다시 유료 사용자 개체를 만드는 우리의 작업에. 우리는 다시 끝까지 가서 "OLOO 패턴", "생성자 함수"패턴 및 클래스를 사용하여 이 기능을 구현할 것입니다. 이런 식으로 우리는 각 패턴과 특징에 대한 절충점을 보게 될 것입니다.


서브 클래 싱에 대해 알아 보겠습니다. ?


OLOO - Sub Classing 


이것은 OLOO 및 팩토리 함수 패턴을 사용하여 작업을 구현 한 것입니다.


const playerFunctions = {
  setScore(newScore) {
    this.score = newScore;
  }
}

function createPlayer(userName, score) {
  const newPlayer = Object.create(playerFunctions);
  newPlayer.userName = userName;
  newPlayer.score = score;
  return newPlayer;
}

const paidPlayerFunctions = {
  setUserName(newName) {
    this.userName = newName;
  }
}

// link paidPlayerFunctions object to createPlayer object
Object.setPrototypeOf(paidPlayerFunctions, playerFunctions);

function createPaidPlayer(userName, score, balance) {
  const paidPlayer = createPlayer(name, score);
  // we need to change the pointer here
  Object.setPrototypeOf(paidPlayer, paidPlayerFunctions);
  paidPlayer.balance = balance;
  return paidPlayer
}

const player1 = createPlayer('sag1v', 700);
const paidPlayer = createPaidPlayer('sag1v', 700, 5);

console.log(player1)
console.log(paidPlayer)


출력됩니다 :


player1 {
  userName: "sag1v",
  score: 700,
  __proto__: playerFunctions {
     setScore: ƒ
  }
}

paidPlayer {
  userName: "sarah",
  score: 900,
  balance: 5,
  __proto__: paidPlayerFunctions {
    setUserName: ƒ,
    __proto__: playerFunctions {
      setScore: ƒ
    }
  }
}


보시다시피 createPlayer 함수 구현은 변경되지 않았지만 createPaidPlayer 함수를 사용하여 몇 가지 트릭을 가져와야 했습니다.


createPaidPlayer에서는 createPlayer를 사용하여 초기 새 객체를 만들고 있으므로 새 플레이어를 만드는 논리를 복제 할 필요는 없지만 불행히도 __proto__를 잘못된 객체에 연결하는 것은 Object이므로이를 수정해야 합니다. setPrototypeOf 메소드. 대상 객체 (__proto__ 포인터를 수정 해야 하는 새로 생성 된 객체)를 전달하고 payPlayerFunctions와 같이 올바른 객체를 지정합니다.


setScore 메소드를 보유하는 playerFunctions 객체에 대한 연결을 끊었으므로 아직 완료되지 않았습니다. 이것이 우리가 다시 Object.setPrototypeOf를 사용하여 paidPlayerFunctions와 playerFunctions를 연결해야 하는 이유입니다. 이를 통해 paidPlayer가 paidPlayerFunctions에 연결된 다음 playerFunctions에 연결되어 있는지 확인합니다.


이것은 2 레벨 체인을 위한 많은 코드입니다. 3 또는 4 레벨 체인을 위한 번거로움을 상상해보십시오.


Constructor Functions - Sub Classing 


이제 생성자 함수로 같은 것을 구현할 수 있습니다.


function Player(userName, score) {
  this.userName = userName;
  this.score = score;
}

Player.prototype.setScore = function(newScore) {
  this.score = newScore;
}

const paidPlayerFunctions = {
  setUserName(newName) {
    this.userName = newName;
  }
}


function PaidPlayer(userName, score, balance) {
  this.balance = balance;
  /* we are calling "Player" without the "new" operator
  but we use the "call" method,
  which allows us to explicitly pass a ref for "this".
  Now the "Player" function will mutate "this"
  and will populate it with the relevant properties */
  Player.call(this, userName, score);
}

PaidPlayer.prototype.setUserName = function(newName) {
  this.userName = newName;
}

// link PaidPlayer.prototype object to Player.prototype object
Object.setPrototypeOf(PaidPlayer.prototype, Player.prototype);


const player1 = new Player('sag1v', 700);
const paidPlayer = new PaidPlayer('sarah', 900, 5);

console.log(player1)
console.log(paidPlayer)


그리고 이전 구현과 비슷한 결과를 얻어야 합니다.


Player {
  userName: "sag1v",
  score: 700,
  __proto__: Player.prototype {
    setScore: ƒ
  }
}

PaidPlayer {
  userName: "sarah",
  score: 900,
  balance: 5,
  __proto__: PaidPlayer.prototype:{
    setUserName: ƒ,
    __proto__: Player.prototype {
      setScore: ƒ
    }
  }
}

이것은 팩토리 함수 패튼에서 얻은 것과 실질적으로 동일한 결과이지만 새로운 오퍼레이터가 우리를 위해 자동화 한 것들입니다. 그것은 우리에게 몇 줄의 코드를 저장했지만 다른 도전을 불러 일으켰습니다.


첫 번째 과제는 Player 함수를 사용하여 초기 Player를 만드는 논리를 얻는 방법이었습니다. 우리는 새로운 연산자 없이 (본능에 대해) 호출하지 않고 .call 메소드를 사용하여 명시 적으로 참조를 전달할 수 있게 했습니다. 이렇게 하면 Player 함수가 생성자 메소드로 작동하지 않으므로 t 새로운 객체를 생성하고 this에 할당


function PaidPlayer(userName, score, balance) {
  this.balance = balance;
  /* we are calling "Player" without the "new" operator
  but we use the "call" method,
  which allows us to explicitly pass a ref for "this".
  Now the "Player" function will mutate "this"
  and will populate it with the relevant properties */
  Player.call(this, userName, score);
}


기본적으로 PaidPlayer 컨텍스트 내에서 새로 생성 된 객체 인 전달 된 객체를 변경하기 위해 여기에서만 Player를 사용하고 있습니다.


우리가 가진 또 다른 과제는 PaidPlayer가 반환 한 인스턴스를 Player 인스턴스의 기능과 연결하는 것입니다. Object.setPrototypeOf를 사용하여 PaidPlayer.prototype을 Player.prototype에 연결했습니다.


// link PaidPlayer.prototype object to Player.prototype object
Object.setPrototypeOf(PaidPlayer.prototype, Player.prototype);


보시다시피, 엔진이 우리를 위해 더 많은 일을 할수록 적은 코드를 작성해야 하지만 추상화의 양이 커질수록 후드 아래에서 일어나는 일을 추적하기가 더 어려워집니다.


Class - Sub Classing 


클래스를 사용하면 훨씬 더 많은 추상화를 얻을 수 있으므로 코드가 줄어 듭니다.


class Player {
  constructor(userName, score) {
    this.userName = userName;
    this.score = score;
  }

  setScore(newScore) {
    this.score = newScore;
  }
}

class PaidPlayer extends Player {
  constructor(userName, score, balance) {
    super(userName, score);
    this.balance = balance;
  }

  setUserName(newName) {
    this.userName = newName;
  }
}



const player1 = new Player('sag1v', 700);
const paidPlayer = new PaidPlayer('sarah', 900, 5);

console.log(player1)
console.log(paidPlayer)

그리고 우리는 생성자 함수와 같은 결과를 얻습니다 :


Player {
  userName: "sag1v",
  score: 700,
  __proto__: Player.prototype {
    setScore: ƒ
  }
}

PaidPlayer {
  userName: "sarah",
  score: 900,
  balance: 5,
  __proto__: PaidPlayer.prototype:{
    setUserName: ƒ,
    __proto__: Player.prototype {
      setScore: ƒ
    }
  }
}


보시다시피 클래스는 생성자 함수에 대한 구문 설탕 일뿐입니다. 글쎄요


문서 에서 이 줄을 기억하십시오.


ECMAScript 2015에 도입 된 JavaScript 클래스는 주로 JavaScript의 기존 프로토 타입 기반 상속에 비해 구문상의 설탕입니다 ...


extends 키워드를 사용할 때 super 기능을 사용해야 했습니다. 왜 그렇습니까?


"Constructor Functions"섹션의 다음 줄을 기억하십시오.


Player.call(this, userName, score)


그래서 super (userName, score)는 그것을 모방하는 일종의 방법입니다.


여기서 좀 더 정확하게 하려면, ES2015에 도입 된 새로운 기능인 Reflect.construct를 사용합니다.


문서에서 인용 :


정적 Reflect.construct () 메소드는 새로운 연산자처럼 작동하지만 함수로 작동합니다. new target (... args)를 호출하는 것과 같습니다. 또한 다른 프로토 타입을 지정하는 추가 옵션도 제공합니다.


따라서 생성자 함수를 더 이상 "해킹"할 필요가 없습니다. 기본적으로 슈퍼는 Reflect.construct로 구현됩니다. 또한 클래스를 확장 할 때 생성자 본문 내에서 아직 초기화되지 않았기 때문에 super ()를 실행하기 전에 이 클래스를 사용할 수 없습니다.


class PaidPlayer extends Player {
  constructor(userName, score, balance) {
    // "this" is uninitialized yet...
    // super refers to Player in this case
    super(userName, score);
    // under the hood super is implemented with Reflect.construct
    // this = Reflect.construct(Player, [userName, score], PaidPlayer);
    this.balance = balance;
  }

  setUserName(newName) {
    this.userName = newName;
  }
}


마무리 


우리는 객체를 연결하고 데이터와 로직을 연결하고 함께 묶을 수 있는 다양한 방법에 대해 배웠습니다. JavaScript에서 "상속"이 작동하는 방식을 보고 __proto__ 속성을 통해 다른 객체에 객체를 연결하는 방법을 보았으며 때로는 여러 수준의 연결이 사용되었습니다.


우리는 그것을 반복해서 봅니다. 더 많은 추상화가 진행 될수록 더 많은 것들이 진행되고 있기 때문에 코드에서 일어나는 일을 추적하기가 더 어려워집니다.


각 패턴에는 장단점이 있습니다.

  • Object.create를 사용하면 더 많은 코드를 작성해야 하지만 객체를 보다 세밀하게 제어 할 수 있습니다. 딥 레벨 체인을 수행하는 것은 지루하지만.
  • 생성자 함수를 사용하면 JavaScript로 자동화 된 작업을 수행하지만 구문이 약간 이상하게 보일 수 있습니다. 또한 새로운 키워드로 함수를 호출해야 합니다. 그렇지 않으면 불쾌한 버그에 직면하게 됩니다. 딥 레벨 체인은 그다지 좋지 않습니다.
  • 클래스를 사용하면 더 깔끔한 구문과 내장 연산자를 통해 새 연산자로 호출했는지 확인할 수 있습니다. 우리가 "상속"을 할 때 클래스가 가장 빛을 발한다. 다른 패턴으로 후프를 점프하는 대신 extends 키워드를 사용하고 super ()를 호출하기 만하면 된다. 문법도 다른 언어에 더 가깝고 배우기 쉬운 것 같습니다. 우리가 보았 듯이 다른 언어의 클래스와는 다르기 때문에 이것이 단점이기도 하지만, 우리는 여전히 오래된 "Prototypal Inheritance"를 많은 추상화 계층으로 사용합니다.

이 기사가 도움이 되었기를 바랍니다. 추가 할 의견이나 제안이나 의견이 있으면 듣고 싶습니다. @ sag1v. ?