JavaScript의 프로토 타입 기반 상속의 좋은 예


89

저는 10 년 넘게 OOP 언어로 프로그래밍 해 왔지만 지금은 JavaScript를 배우고 있으며 프로토 타입 기반 상속을 접한 것은 이번이 처음입니다. 저는 좋은 코드를 공부함으로써 가장 빨리 배우는 경향이 있습니다. 프로토 타입 상속을 적절하게 사용하는 JavaScript 애플리케이션 (또는 라이브러리)의 잘 작성된 예는 무엇입니까? 그리고 프로토 타입 상속이 어떻게 / 어디에서 사용되는지 (간단하게) 설명 해주실 수 있나요?


1
Base 라이브러리를 확인해 보셨나요? 정말 멋지고 아주 작습니다. 마음에 들면 내 답변을 답변으로 표시해보세요. TIA, 롤랜드.
Roland Bouman

나는 당신과 같은 배에있는 것 같아요. 저는 또한이 프로토 타입 언어에 대해 조금 배우고 싶습니다. 단지 oop 프레임 워크 나 이와 유사한 것에 만 국한되지 않고, 심지어 그것들이 훌륭하고 모든 것이 우리는 배워야합니다. 내가 그것을 사용하더라도 일부 프레임 워크가 나를 위해 그렇게하지 않습니다. 그러나 새로운 방법으로 새로운 언어로 새로운 것을 만드는 방법을 배우고 상자 밖에서 생각하십시오. 나는 당신의 스타일을 좋아합니다. 나는 나를 돕고 아마도 당신을 도우려고 노력할 것입니다. 뭔가를 찾으면 바로 알려 드리겠습니다.
marcelo-ferraz

답변:


48

Douglas Crockford에는 JavaScript Prototypal Inheritance 에 대한 멋진 페이지가 있습니다 .

5 년 전에 JavaScript로 Classical Inheritance 를 작성 했습니다. JavaScript는 클래스가없는 프로토 타입 언어이며 클래식 시스템을 시뮬레이션하기에 충분한 표현력을 가지고 있음을 보여주었습니다. 내 프로그래밍 스타일은 좋은 프로그래머가해야하는 것처럼 그 이후로 발전했습니다. 나는 프로토 타입주의를 완전히 포용하는 법을 배웠고 고전적 모델의 한계에서 벗어나 자신을 해방 시켰습니다.

Dean Edward의 Base.js , Mootools의 Class 또는 John Resig의 Simple Inheritance 작업은 JavaScript에서 고전적인 상속 을 수행하는 방법 입니다.


단순히 newObj = Object.create(oldObj);수업이없는 것을 원하면 어떻습니까? 그렇지 않으면 oldObj생성자 함수의 프로토 타입 객체로 대체 하면 작동합니까?
Cyker 2015

76

언급했듯이 Douglas Crockford의 영화는 이유에 대한 좋은 설명을 제공하며 방법을 다룹니다. 그러나 몇 줄의 JavaScript에 넣으려면 :

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

그러나이 방법의 문제점은 개체를 만들 때마다 개체를 다시 생성한다는 것입니다. 또 다른 접근 방식은 다음과 같이 프로토 타입 스택에서 객체를 선언하는 것입니다.

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = ​function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

자기 성찰과 관련하여 약간의 단점이 있습니다. testOne을 덤핑하면 정보가 덜 유용합니다. 또한 "testOne"의 개인 속성 "privateVariable"은 모든 경우에 공유되며 shesek의 답변에서 유용하게 언급됩니다.


3
testOne privateVariableIIFE 범위의 변수이며 모든 인스턴스에서 공유되므로 인스턴스 별 데이터를 저장해서는 안됩니다. (testTwo에서는 testTwo ()를 호출 할 때마다 인스턴스별로 새로운 범위가 생성되므로 인스턴스별로
다릅니다.

난 당신이 다른 접근 방식을 보여 주었다 때문에 upvoted하고 사본을 만들기 때문에 왜 사용하지 않는
Murphy316

매번 개체를 다시 만드는 문제는 주로 모든 새 개체에 대해 다시 생성되는 메서드 때문입니다. 그러나에 메서드를 정의하여 문제를 완화 할 수 있습니다 Dog.prototype. 따라서를 사용하는 대신 함수 외부에서 this.bark = function () {...}할 수 있습니다 . (더 자세한 내용을 참조하십시오 이 답변 )Dot.prototype.bark = function () {...}Dog
C 황에게

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
아마도이 링크를 답변과 함께 추가하면 그림이 더 완성 될 것입니다. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Dynom

14

나는 YUI 와 Dean Edward의 Base도서관을 살펴볼 것입니다 : http://dean.edwards.name/weblog/2006/03/base/

YUI의 경우 lang 모듈 , esp를 빠르게 살펴볼 수 있습니다 . YAHOO.lang.extend의 방법. 그런 다음 일부 위젯 또는 유틸리티의 소스를 찾아보고 해당 방법을 사용하는 방법을 볼 수 있습니다.


YUI 2는 2011 년부터 더 이상 사용되지 않으므로에 대한 링크 lang가 약간 끊어졌습니다. 누구든지 YUI 3를 위해 그것을 고칠 필요가 있습니까?
ACK

yui 3의 lang에는 확장 메서드가없는 것 같습니다. 그러나 대답은 구현을 예제로 사용하려는 것이므로 버전은 중요하지 않습니다.
eMBee 2019


5

Mixu의 Node 책 ( http://book.mixu.net/node/ch6.html ) 에서 찾은 가장 명확한 예입니다 .

상속보다 구성을 선호합니다.

구성-개체의 기능은 다른 개체의 인스턴스를 포함하여 여러 클래스의 집합체로 구성됩니다. 상속-객체의 기능은 자체 기능과 상위 클래스의 기능으로 구성됩니다. 상속이 필요한 경우 일반 오래된 JS를 사용하십시오.

상속을 구현해야한다면 적어도 또 다른 비표준 구현 / 마법 기능을 사용하지 마십시오. 다음은 순수 ES3에서 합리적인 상속 팩스를 구현할 수있는 방법입니다 (프로토 타입에서 속성을 정의하지 않는 규칙을 따르는 한).

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

이것은 고전적인 상속과 같은 것은 아니지만 표준적이고 이해하기 쉬운 자바 스크립트이며 사람들이 주로 찾는 기능, 즉 체인 생성자와 수퍼 클래스의 메소드를 호출하는 기능을 가지고 있습니다.


4

ES6 classextends

ES6 classextends이전에 가능한 프로토 타입 체인 조작 단지 구문 설탕, 그래서 틀림없이 가장 표준적인 설정입니다.

먼저 https://stackoverflow.com/a/23877420/895245. 에서 프로토 타입 체인 및 속성 조회에 대해 자세히 알아보십시오 .

이제 무슨 일이 일어나는지 분해 해 보겠습니다.

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

사전 정의 된 모든 개체가없는 단순화 된 다이어그램 :

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

내가 본 최고의 예는 Douglas Crockford의 JavaScript : The Good Parts 입니다. 언어에 대한 균형 잡힌 견해를 얻기 위해 구매할 가치가 있습니다.

Douglas Crockford 는 JSON 형식을 담당하고 Yahoo에서 JavaScript 전문가로 일하고 있습니다.


7
책임? 거의 "유죄"처럼 들립니다. :)
Roland Bouman

@Roland 저는 JSON이 데이터를 저장하는 데 아주 좋은 장황하지 않은 형식이라고 생각합니다. , 형식은 2002 년에 증기 뒷면의 구성 설정에 그는 분명하지만 그것을 발명하지 않았다 있었다
크리스 S

Chris S, 저도 그렇게 생각합니다. 점점 더 자주 XML을 교환 형식으로 건너 뛰고 곧바로 JSON으로 이동할 수 있기를 바랍니다.
Roland Bouman

3
그다지 발명 할 사항은 아닙니다. JSON은 1997 년
Tim Down

@ 시간 좋은 점-시작 이후 거기에 있었다는 것을 몰랐습니다
Chris S


0

Javascript에서 Prototype 기반 상속의 예를 추가합니다.

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

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

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

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

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6는 생성자와 슈퍼 키워드를 사용하여 훨씬 쉽게 상속을 구현합니다.

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