자바 스크립트 상속 : 슈퍼 생성자를 호출하거나 프로토 타입 체인을 사용합니까?


82

아주 최근에 MDC에서 JavaScript 호출 사용에 대해 읽었습니다.

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

아래에 표시된 예제의 링크 하나, 나는 여전히 이해하지 못합니다.

왜 여기에서 상속을 사용하고 있습니까?

Prod_dept.prototype = new Product();

이것이 필요한가요? 슈퍼 건설자에게 전화가 왔기 때문에

Prod_dept()

어쨌든 이렇게

Product.call

이것은 일반적인 행동에서 벗어난 것입니까? 슈퍼 생성자 호출을 사용하거나 프로토 타입 체인을 사용하는 것이 언제 더 낫습니까?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

더 명확하게 해주셔서 감사합니다


사용한 방법은 거의 맞지만 new 키워드를 사용하여 기본을 인스턴스화하는 대신 Object.create ()를 사용할 수 있습니다 (기본 생성자에 인수가 필요한 경우 문제가 발생할 수 있음). : 내 블로그에 자세한 내용이 ncombo.wordpress.com/2013/07/11/...

2
또한 Product ()는 효과적으로 두 번 호출됩니다.
event_jr

답변:


109

실제 질문에 대한 답은 다음 두 가지를 모두 수행해야한다는 것입니다.

  • 프로토 타입을 부모의 인스턴스로 설정하면 프로토 타입 체인 (상속)이 초기화되며, 이는 프로토 타입 객체가 공유되기 때문에 한 번만 수행됩니다.
  • 부모의 생성자를 호출하면 객체 자체가 초기화됩니다. 이는 모든 인스턴스화와 함께 수행됩니다 (생성 할 때마다 다른 매개 변수를 전달할 수 있음).

따라서 상속을 설정할 때 부모의 생성자를 호출하면 안됩니다. 다른 개체에서 상속 된 개체를 인스턴스화 할 때만.

Chris Morgan의 답변은 거의 완성되어 작은 세부 사항 (생성자 속성)이 누락되었습니다. 상속을 설정하는 방법을 제안하겠습니다.

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

수업을 만들 때 더 많은 구문 설탕을 보려면 내 블로그 게시물을 참조하십시오. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Ext-JS 및 http://www.uselesspickles.com/class_library/ 에서 복사 한 기술 과 https://stackoverflow.com/users/1397311/ccnokes 의 주석


6
EcmaScript5 + (모든 최신 브라우저)에서 다음과 같이 정의하면 열거 불가능하게 만들 수 있습니다. Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); 이렇게하면 자바 스크립트가 함수의 새 인스턴스를 만들 때와 정확히 동일한 "동작"을 얻을 수 있습니다 (생성자는 열거 가능으로 설정 됨). = 거짓 자동)
Adaptabi

2
확장 방법을 두 줄로 단순화 할 수 없습니까? 즉 : sub.prototype = Object.create (base.prototype); sub.prototype.constructor = 하위;
Appetere 2010 년

@Steve 예, 제가 처음이 글을 썼을 때 Object.create잘 지원되지 않았습니다 ... 업데이트 할 수 있습니다. 대부분의 Object.createpolyfill은 내가 원래 보여준 기술을 사용하여 구현됩니다.
Juan Mendes 2013 년

1
따라서 자식 개체와 해당 인스턴스 (이 경우 "Dog"개체)에만 메서드를 추가하려면 다음과 같이 확장 함수 내에서 두 프로토 타입을 병합하면됩니다. jsfiddle.net/ccnokes/75f9P ?
ccnokes 2014

1
@ elad.chen 내가 프로토 타입의 응답 체인에서 설명한 접근 방식, mixins는 일반적으로 프로토 타입이 아닌 인스턴스에 모든 속성을 복사합니다. 참조 stackoverflow.com/questions/7506210/...
후안 멘데스

30

이를 수행하는 이상적인 방법 은 생성자를 호출하기 때문에 하지 않는 것 입니다. 따라서 이상적인 방법은 생성자를 제외하고 다음과 같이 복제하는 것입니다.Prod_dept.prototype = new Product();Product

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

그런 다음 생성시에 슈퍼 생성자가 호출되는데, 이는 원하는대로 매개 변수를 전달할 수 있기 때문입니다.

Google Closure Library와 같은 것을 보면 그것이 어떻게 작동하는지 알 수 있습니다.


상속을 설정하는 데 사용되는 생성자를 대리 생성자라고 부릅니다. 귀하의 예는 아직 제대로 생성자 감지 할 수 있도록 상속을 설정 한 후 constructor 속성을 다시 잊어
후안 멘데스

1
@Juan : 좋습니다 Prod_dept.prototype.constructor = Prod_dept;..
Chris Morgan

@ChrisMorgan 샘플의 마지막 줄을 이해하는 데 문제가 있습니다 Prod_dept.prototype.constructor = Prod_dept;. 우선, 왜 필요하고 Prod_dept대신 왜 가리키는가 Product?
Lasse Christiansen

1
@ LasseChristiansen-sw_lasse : Prod_dept.prototype출력의 프로토 타입으로 사용되는 new Prod_dept(). (일반적으로이 프로토 타입은 instance.__proto__구현 세부 사항이지만 으로 사용할 수 있습니다 .) 이유는 constructor— 언어의 표준 부분이므로 일관성을 위해 제공되어야합니다. 기본적으로는 맞지만 프로토 타입을 완전히 교체하기 때문에 올바른 값을 다시 할당해야합니다. 그렇지 않으면 일부 항목이 정상이 아닙니다 (이 경우 Prod_dept인스턴스에 this.constructor == Product.
Chris Morgan

6

JavaScript에서 객체 지향 프로그래밍을 수행했다면 다음과 같이 클래스를 만들 수 있음을 알게 될 것입니다.

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

지금까지 우리 클래스 사람은 두 가지 속성 만 가지고 있으며 몇 가지 메서드를 제공 할 것입니다. 이를 수행하는 깨끗한 방법은 '프로토 타입'객체를 사용하는 것입니다. JavaScript 1.1부터 프로토 타입 객체가 JavaScript로 도입되었습니다. 이것은 개체의 모든 인스턴스에 사용자 지정 속성 및 메서드를 추가하는 프로세스를 단순화하는 기본 제공 개체입니다. 다음과 같이 'prototype'객체를 사용하여 클래스에 두 가지 메서드를 추가해 보겠습니다.

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

이제 Person 클래스를 정의했습니다. Person에서 일부 속성을 상속하는 Manager라는 다른 클래스를 정의하려면 어떻게해야합니까? Manager 클래스를 정의 할 때이 모든 속성을 다시 정의 할 필요가 없습니다. Person 클래스에서 상속하도록 설정할 수 있습니다. JavaScript에는 상속이 내장되어 있지 않지만 다음과 같이 상속을 구현하는 기술을 사용할 수 있습니다.

Inheritance_Manager = {};// 상속 관리자 클래스를 만듭니다 (이름은 임의적 임).

이제 상속 클래스에 baseClass 및 subClassas 인수를받는 extend라는 메서드를 제공하겠습니다. extend 메소드 내에서 inheritance function inheritance () {}라는 내부 클래스를 생성합니다. 이 내부 클래스를 사용하는 이유는 baseClass와 subClass 프로토 타입 간의 혼동을 피하기 위해서입니다. 다음으로 상속 클래스의 프로토 타입이 다음 코드와 같이 baseClass 프로토 타입을 가리 키도록합니다. inheritance.prototype = baseClass. 원기; 그런 다음 상속 프로토 타입을 다음과 같이 하위 클래스 프로토 타입에 복사합니다. subClass.prototype = new inheritance (); 다음은 다음과 같이 하위 클래스의 생성자를 지정하는 것입니다. subClass.prototype.constructor = subClass; 서브 클래스 프로토 타이핑이 끝나면 다음 두 줄의 코드를 지정하여 일부 기본 클래스 포인터를 설정할 수 있습니다.

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

다음은 extend 함수의 전체 코드입니다.

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

이제 상속을 구현 했으므로이를 사용하여 클래스를 확장 할 수 있습니다. 이 경우 Person 클래스를 다음과 같이 Manager 클래스로 확장 할 것입니다.

Manager 클래스를 정의합니다.

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

우리는 그것을 Person 형태로 상속합니다.

Inheritance_Manager.extend(Manager, Person);

눈치 채 셨다면, Inheritance_Manager 클래스의 extend 메소드를 호출하고 우리의 경우에는 subClass Manager를 전달한 다음 baseClass Person을 전달했습니다. 여기서 순서는 매우 중요합니다. 스왑하면 상속이 의도 한대로 작동하지 않습니다. 또한 실제로 하위 클래스를 정의하기 전에이 상속을 지정해야합니다. 이제 하위 클래스를 정의하겠습니다.

아래와 같이 더 많은 메소드를 추가 할 수 있습니다. Manager 클래스는 상속되기 때문에 항상 Person 클래스에 정의 된 메서드와 속성을 갖습니다.

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

이제 테스트를 위해 Person 클래스와 상속 된 클래스 Manager에서 하나씩 두 개의 객체를 생성 해 보겠습니다.

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx 에서 전체 코드와 더 많은 의견을 얻으십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.