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

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



답변

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

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

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


답변

언급했듯이 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의 답변에서 유용하게 언급됩니다.


답변

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);


답변

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

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


답변

마이크로 소프트의 ASP.NET Ajax 라이브러리, http://www.asp.net/ajax/도 있습니다.

Create Advanced Web Applications With Object-Oriented Techniques를 포함하여 좋은 MSDN 기사도 많이 있습니다 .


답변

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();

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


답변

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