웹사이트 검색

JavaScript의 프로토타입 및 상속 이해


소개

JavaScript는 프로토타입 기반 언어입니다. 즉, 복제 및 확장 기능이 있는 일반화된 개체를 통해 개체 속성 및 메서드를 공유할 수 있습니다. 이를 프로토타입 상속이라고 하며 클래스 상속과 다릅니다. 널리 사용되는 객체 지향 프로그래밍 언어 중에서 JavaScript는 상대적으로 고유합니다. PHP, Python 및 Java와 같은 다른 유명한 언어는 대신 클래스를 객체의 청사진으로 정의하는 클래스 기반 언어이기 때문입니다.

이 자습서에서는 개체 프로토타입이 무엇인지, 생성자 함수를 사용하여 프로토타입을 새 개체로 확장하는 방법을 배웁니다. 상속과 프로토타입 체인에 대해서도 배웁니다.

자바스크립트 프로토타입

JavaScript의 개체 이해에서 개체 데이터 유형, 개체를 만드는 방법, 개체 속성에 액세스하고 수정하는 방법을 살펴보았습니다. 이제 프로토타입을 사용하여 개체를 확장하는 방법을 알아봅니다.

JavaScript의 모든 개체에는 [[Prototype]]라는 내부 속성이 있습니다. 비어 있는 새 개체를 만들어 이를 증명할 수 있습니다.

let x = {};

이것은 일반적으로 객체를 생성하는 방법이지만 이를 수행하는 또 다른 방법은 객체 생성자를 사용하는 것입니다: let x = new Object().

[[Prototype]]을 묶는 이중 대괄호는 내부 속성이며 코드에서 직접 액세스할 수 없음을 나타냅니다.

이 새로 생성된 개체의 [[Prototype]]을 찾기 위해 getPrototypeOf() 메서드를 사용합니다.

Object.getPrototypeOf(x);

출력은 여러 기본 제공 속성 및 메서드로 구성됩니다.

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

[[Prototype]]을 찾는 또 다른 방법은 __proto__ 속성을 이용하는 것입니다. __proto__는 객체의 내부 [[Prototype]]을 노출시키는 속성입니다.

.__proto__는 레거시 기능이며 프로덕션 코드에서 사용해서는 안 되며 모든 최신 브라우저에 있는 것은 아닙니다. 그러나 데모 목적으로 이 문서 전체에서 사용할 수 있습니다.

x.__proto__;

출력은 getPrototypeOf()를 사용한 것과 동일합니다.

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

JavaScript의 모든 개체에는 [[Prototype]]이 있어야 두 개 이상의 개체를 연결할 수 있는 방법이 생성됩니다.

생성하는 객체에는 날짜배열과 같은 내장 객체와 마찬가지로 [[Prototype]]이 있습니다. 이 자습서의 뒷부분에서 볼 수 있듯이 prototype 속성을 통해 한 개체에서 다른 개체로 이 내부 속성에 대한 참조를 만들 수 있습니다.

프로토타입 상속

개체의 속성이나 메서드에 액세스하려고 하면 JavaScript는 먼저 개체 자체를 검색하고 찾을 수 없는 경우 개체의 [[Prototype]]를 검색합니다. 개체와 해당 [[Prototype]]을 모두 참조한 후에도 여전히 일치하는 항목이 없으면 JavaScript는 연결된 개체의 프로토타입을 확인하고 프로토타입 체인의 끝에 도달할 때까지 검색을 계속합니다.

프로토타입 체인의 끝에는 Object가 있습니다. 체인의 끝을 넘어 검색을 시도하면 null이 됩니다.

이 예에서 xObject에서 상속되는 빈 개체입니다. xtoString()과 같이 Object에 있는 모든 속성이나 메서드를 사용할 수 있습니다.

x.toString();
Output
[object Object]

이 프로토타입 체인은 단 하나의 링크 길이입니다. x -> 객체. 두 개의 [[Prototype]] 속성을 함께 연결하려고 하면 null이 되기 때문에 이를 알고 있습니다.

x.__proto__.__proto__;
Output
null

다른 유형의 개체를 살펴보겠습니다. JavaScript에서 배열로 작업한 경험이 있다면 배열에 pop()push()와 같은 많은 내장 메서드가 있다는 것을 알고 있을 것입니다. 새 배열을 만들 때 이러한 메서드에 액세스할 수 있는 이유는 생성하는 모든 배열이 Array.prototype의 속성 및 메서드에 액세스할 수 있기 때문입니다.

새 어레이를 생성하여 이를 테스트할 수 있습니다.

let y = [];

let y = new Array()와 같이 배열 생성자로 작성할 수도 있습니다.

새로운 y 배열의 [[Prototype]]을 살펴보면 x보다 더 많은 속성과 메서드가 있음을 알 수 있습니다. 코드> 객체. Array.prototype에서 모든 것을 물려받았습니다.

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

프로토타입에서 Array()로 설정된 constructor 속성을 확인할 수 있습니다. constructor 속성은 함수에서 개체를 구성하는 데 사용되는 메커니즘인 개체의 생성자 함수를 반환합니다.

이 경우 프로토타입 체인이 더 길기 때문에 이제 두 개의 프로토타입을 함께 연결할 수 있습니다. y -> 배열 -> 객체처럼 보입니다.

y.__proto__.__proto__;
Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

이 체인은 이제 Object.prototype을 참조합니다. 생성자 함수의 prototype 속성에 대해 내부 [[Prototype]]을 테스트하여 동일한 것을 참조하는지 확인할 수 있습니다.

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true

isPrototypeOf() 메서드를 사용하여 이를 수행할 수도 있습니다.

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true

instanceof 연산자를 사용하여 생성자의 prototype 속성이 개체의 프로토타입 체인 내 어디에나 나타나는지 여부를 테스트할 수 있습니다.

y instanceof Array; // true

요약하면 모든 JavaScript 개체에는 숨겨진 내부 [[Prototype]] 속성이 있습니다(일부 브라우저에서는 __proto__를 통해 노출될 수 있음). 개체는 확장될 수 있으며 생성자의 [[Prototype]]에 있는 속성과 메서드를 상속합니다.

이러한 프로토타입은 연결될 수 있으며 각각의 추가 개체는 체인 전체에서 모든 것을 상속합니다. 체인은 Object.prototype로 끝납니다.

생성자 함수

생성자 함수는 새 객체를 생성하는 데 사용되는 함수입니다. new 연산자는 생성자 함수를 기반으로 새 인스턴스를 만드는 데 사용됩니다. 우리는 new Array()new Date()와 같은 일부 내장 JavaScript 생성자를 보았지만 새로운 빌드를 위한 자체 사용자 지정 템플릿을 만들 수도 있습니다. 사물.

예를 들어 매우 간단한 텍스트 기반 롤플레잉 게임을 만들고 있다고 가정해 보겠습니다. 사용자는 캐릭터를 선택한 다음 전사, 치료사, 도둑 등과 같은 캐릭터 클래스를 선택할 수 있습니다.

각 캐릭터는 이름, 레벨, 체력 등 많은 특성을 공유하므로 생성자를 템플릿으로 만드는 것이 좋습니다. 그러나 각 캐릭터 클래스는 매우 다른 능력을 가질 수 있으므로 각 캐릭터가 자신의 능력만 사용할 수 있도록 하고 싶습니다. 프로토타입 상속 및 생성자로 이를 수행하는 방법을 살펴보겠습니다.

시작하려면 생성자 함수는 일반 함수일 뿐입니다. new 키워드가 있는 인스턴스에서 호출하면 생성자가 됩니다. JavaScript에서는 관례에 따라 생성자 함수의 첫 글자를 대문자로 씁니다.

// Initialize a constructor function for a new Hero
function Hero(name, level) {
  this.name = name;
  this.level = level;
}

namelevel의 두 매개 변수를 사용하여 Hero라는 생성자 함수를 만들었습니다. 모든 캐릭터에는 이름과 레벨이 있으므로 각각의 새 캐릭터가 이러한 속성을 갖는 것이 좋습니다. this 키워드는 생성된 새 인스턴스를 참조하므로 this.namename 매개변수로 설정하면 새 개체가 이름 속성 집합.

이제 new를 사용하여 새 인스턴스를 만들 수 있습니다.

let hero1 = new Hero('Bjorn', 1);

hero1을 콘솔로 내보내면 예상대로 설정된 새 속성으로 새 개체가 생성된 것을 볼 수 있습니다.

Output
Hero {name: "Bjorn", level: 1}

이제 hero1[[Prototype]]을 가져오면 constructorHero()로 볼 수 있습니다. (기억하세요, 이것은 hero1.__proto__와 같은 입력을 가지지만, 사용하기에 적절한 방법입니다.)

Object.getPrototypeOf(hero1);
Output
constructor: ƒ Hero(name, level)

생성자에서 메서드가 아닌 속성만 정의한 것을 알 수 있습니다. 효율성과 코드 가독성을 높이기 위해 프로토타입에 메서드를 정의하는 것은 JavaScript에서 일반적인 관행입니다.

prototype을 사용하여 Hero에 메서드를 추가할 수 있습니다. greet() 메서드를 생성합니다.

...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}

greet()Heroprototype에 있고 hero1Hero의 인스턴스이기 때문에, 메서드는 hero1에서 사용할 수 있습니다.

hero1.greet();
Output
"Bjorn says hello."

Hero의 [[Prototype]]을 검사하면 이제 사용 가능한 옵션으로 greet()가 표시됩니다.

이것은 좋지만 이제 영웅이 사용할 캐릭터 클래스를 만들고 싶습니다. 클래스마다 능력이 다르기 때문에 모든 클래스의 모든 능력을 Hero 생성자에 넣는 것은 이치에 맞지 않습니다. 우리는 새로운 생성자 함수를 만들고 싶지만 원래 Hero에 연결되기를 원합니다.

call() 메서드를 사용하여 한 생성자의 속성을 다른 생성자로 복사할 수 있습니다. Warrior와 Healer 생성자를 만들어 봅시다.

...
// Initialize Warrior constructor
function Warrior(name, level, weapon) {
  // Chain constructor with call
  Hero.call(this, name, level);

  // Add a new property
  this.weapon = weapon;
}

// Initialize Healer constructor
function Healer(name, level, spell) {
  Hero.call(this, name, level);

  this.spell = spell;
}

새로운 두 생성자 모두 이제 Hero 속성과 몇 가지 독특한 속성을 가집니다. attack() 메서드를 Warrior에 추가하고 heal() 메서드를 Healer에 추가합니다.

...
Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}

이 시점에서 사용 가능한 두 가지 새로운 캐릭터 클래스로 캐릭터를 생성합니다.

const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

hero1은 이제 새로운 속성을 가진 전사로 인식됩니다.

Output
Warrior {name: "Bjorn", level: 1, weapon: "axe"}

Warrior 프로토타입에 설정한 새 메서드를 사용할 수 있습니다.

hero1.attack();
Console
"Bjorn attacks with the axe."

그러나 프로토타입 체인에서 더 아래에 있는 메서드를 사용하려고 하면 어떻게 될까요?

hero1.greet();
Output
Uncaught TypeError: hero1.greet is not a function

프로토타입 속성 및 메서드는 call()을 사용하여 생성자를 연결할 때 자동으로 연결되지 않습니다. Object.setPropertyOf()를 사용하여 Hero 생성자의 속성을 WarriorHealer 생성자에 연결합니다. , 추가 메서드 앞에 두어야 합니다.

...
Object.setPrototypeOf(Warrior.prototype, Hero.prototype);
Object.setPrototypeOf(Healer.prototype, Hero.prototype);

// All other prototype methods added below
...

이제 Warrior 또는 Healer 인스턴스에서 Hero의 프로토타입 메서드를 성공적으로 사용할 수 있습니다.

hero1.greet();
Output
"Bjorn says hello."

다음은 캐릭터 생성 페이지의 전체 코드입니다.

// Initialize constructor functions
function Hero(name, level) {
  this.name = name;
  this.level = level;
}

function Warrior(name, level, weapon) {
  Hero.call(this, name, level);

  this.weapon = weapon;
}

function Healer(name, level, spell) {
  Hero.call(this, name, level);

  this.spell = spell;
}

// Link prototypes and add prototype methods
Object.setPrototypeOf(Warrior.prototype, Hero.prototype);
Object.setPrototypeOf(Healer.prototype, Hero.prototype);

Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}

Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}

// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

이 코드를 사용하여 기본 속성을 사용하여 Hero 생성자를 만들고 원래 생성자에서 WarriorHealer라는 두 개의 캐릭터 생성자를 만들고 추가했습니다. 프로토타입에 메서드를 추가하고 개별 캐릭터 인스턴스를 생성했습니다.

결론

JavaScript는 프로토타입 기반 언어이며 다른 많은 객체 지향 언어에서 사용하는 기존의 클래스 기반 패러다임과 다르게 작동합니다.

이 튜토리얼에서는 JavaScript에서 프로토타입이 작동하는 방식과 모든 개체가 공유하는 숨겨진 [[Prototype]] 속성을 통해 개체 속성과 메서드를 연결하는 방법을 배웠습니다. 또한 사용자 지정 생성자 함수를 만드는 방법과 프로토타입 상속이 속성 및 메서드 값을 전달하는 방식을 배웠습니다.