JavaScript Inheritance
DouglasCrockford
www.crockford.com
And you think you're so clever and classless and free
--John Lennon
JavaScript一种没有类的,面向对象的语言,它使用原型继承来代替类继承。这个可能对受过传统的面向对象语言(如C++和Java)训练的程序员来说有点迷惑。JavaScript的原型继承比类继承有更强大的表现力,现在就让我们来看看。
Java
|
JavaScript
|
强类型
|
弱类型
|
静态
|
动态
|
基于类
|
基于原型
|
类
|
函数
|
构造器
|
函数
|
方法
|
函数
|
먼저, 우리는 왜 상속에 그토록 관심을 두는 걸까요? 두 가지 주요 이유가 있습니다. 첫 번째는 유형에 유리한 것입니다. 언어 시스템이 유사 유형 참조 변환cast을 자동으로 수행할 수 있기를 바랍니다. 프로그램이 개체 참조를 명시적으로 변환하도록 요구하는 형식 시스템에서 작은 형식 안전성을 얻을 수 있습니다. 이는 강력한 형식의 언어에서 가장 중요한 점이지만, 클래스 참조에 캐스팅이 필요하지 않은 JavaScript와 같은 약한 형식의 언어와는 아무런 관련이 없습니다.
두 번째 이유는 코드 재사용입니다. 프로그램에서 많은 개체가 동일한 메서드를 구현하는 경우가 종종 있습니다. 클래스를 사용하면 단일 정의 세트로 객체를 생성할 수 있습니다. 객체가 다른 객체에 포함된 객체를 포함하는 것도 일반적이지만 차이점은 소수의 메서드를 추가하거나 수정한 것뿐입니다. 클래스 상속은 이를 위해 매우 유용하지만 프로토타입 상속은 훨씬 더 유용합니다.
이를 보여주기 위해 일반 수업 언어처럼 코드를 작성할 수 있는 작은 "최적의 장소"를 소개하겠습니다. 그런 다음 클래스 언어에서는 찾을 수 없는 몇 가지 유용한 패턴을 보여줍니다. 마지막으로 이 '디저트'에 대해 설명하겠습니다.
클래스 상속
먼저 멤버 값에 대한 get 및 set 메서드와 값을 괄호로 묶는 toString 메서드가 있는 Parenizor 클래스를 만듭니다.
function Parenizor(value) {
this.setValue (값);
}
Parenizor.method('setValue', function (값) {
this.value = value;
return this;
}); >Parenizor.method('getValue', function () {
return this.value;
})
Parenizor.method('toString', function () {
return '(' this .getValue( ) ')';
});
이 구문은 유용하지 않을 수 있지만 클래스의 형태를 보면 쉽습니다. 메소드 메소드는 메소드 이름과 함수를 취해 이를 클래스에 공개 메소드로 넣습니다.
이제
myParenizor = new Parenizor( 0);
myString = myParenizor.toString();
예상대로 myString은 "(0)"입니다.
이제 Parenizor에서 상속되는 또 다른 클래스를 생성하겠습니다. 값이 0이거나 비어 있으면 toString 메서드가 "-0-"을 생성한다는 점을 제외하면 기본적으로 동일합니다.
function ZParenizor(value) {
this.setValue (값);
}
ZParenizor.inherits(Parenizor);
ZParenizor.method("e;toString"e;, function () {
if (this.getValue() ) {
return this.uber('toString');
return "-0-";
})
상속 방법은 다음과 유사합니다. Java의 확장입니다. uber 방법은 Java의 super와 유사합니다. 메소드 호출을 상위 클래스의 메소드로 만듭니다(예약어와의 충돌을 피하기 위해 이름이 변경됨).
이렇게 작성하면 됩니다
myZParenizor = new ZParenizor( 0);
myString = myZParenizor.toString();
이번에는 myString이 "-0-"입니다.
JavaScript에는 클래스가 없습니다. 하지만 우리는 프로그래밍 방식으로 이 목적을 달성할 수 있습니다.
다중 상속
함수의 프로토타입 객체를 조작하여 다중 상속을 구현할 수 있습니다. 혼합 다중 상속은 구현하기 어렵고 이름 충돌의 위험이 있습니다. JavaScript에서 혼합 다중 상속을 구현할 수 있지만 이 예에서는 스위스 상속이라는 보다 표준화된 형식을 사용합니다.
값이 지정된 범위 내에 있는지 확인하는 setValue 메서드가 있는 NumberValue 클래스가 있다고 가정합니다. 내부에서 적절한 경우 예외를 발생시킵니다. ZParenizor에 대한 setValue 및 setRange 메소드만 있으면 됩니다. 우리는 확실히 toString 메소드를 원하지 않습니다.
ZParenizor. 스위스(NumberValue, 'setValue', 'setRange')
필수 메소드만 추가됩니다.
기생 상속
이는 ZParenizor 클래스를 작성하는 또 다른 방법입니다. Parenizor를 상속받지 않고 Parenizor 생성자를 호출하는 생성자를 작성하고 결과를 수정한 후 최종적으로 결과를 반환합니다. 이 생성자는 공용 메서드가 아닌 권한 있는 메서드를 추가합니다.
function ZParenizor2(value) {
var self = new Parenizor(value);
self.toString = function () {
if (this.getValue()) {
return this.uber('toString')
}
return "-0-"
};
return self;
}
클래스 상속은 "is..." 관계인 반면, 기생 상속은 관계입니다. "원래는 그랬고 지금은..." 관계에 대해. 생성자는 객체 생성에 큰 역할을 합니다. super 키워드 대신 uber는 여전히 권한 있는 방법으로 작동합니다.
클래스 확장
JavaScript의 동적 특성으로 인해 기존 클래스에 메서드를 추가하거나 교체할 수 있습니다. 언제든지 메소드를 호출할 수 있습니다. 언제든지 수업을 연장할 수 있습니다. 상속은 이런 식으로 작동하지 않습니다. 따라서 우리는 Java의 확장(확장이라고도 부르지만 동일한 것은 아님)과의 혼동을 피하기 위해 이 상황을 "클래스 확장"이라고 부릅니다.
객체 확장
정적 객체 지향 언어에서 한 객체를 다른 객체와 다르게 하려면 새 클래스를 만들어야 합니다. 하지만 JavaScript에서는 새 클래스를 만들지 않고도 개별 개체에 메서드를 추가할 수 있습니다. 가능한 한 적은 수의 클래스를 작성할 수 있고 클래스를 더 간단하게 작성할 수 있기 때문에 이는 엄청난 힘을 가지고 있습니다. 해시 테이블과 같은 JavaScript 개체를 생각해 보십시오. 언제든지 새로운 값을 추가할 수 있습니다. 값이 함수이면 메서드가 됩니다.
위의 예에서는 ZParenizor 클래스가 전혀 필요하지 않습니다. 인스턴스를 간단히 수정하면 됩니다.
myParenizor = new Parenizor(0); 🎜>myParenizor .toString = function () {
if (this.getValue()) {
return this.uber('toString')
}
return "-0-"; 🎜>} ;
myString = myParenizor.toString();
상속을 사용하지 않고 myParenizor 인스턴스에 toString 메서드를 추가했습니다. 언어가 유형이 없기 때문에 개별 인스턴스를 발전시킬 수 있습니다.
작은 디저트
위의 예제가 작동하도록 하기 위해 네 가지 "디저트" 메소드를 작성했습니다. 첫째, 메소드 메소드는 클래스에 인스턴스 메소드를 추가할 수 있습니다.
Function.prototype.method = 함수(이름 , func) {
this.prototype[name] = func;
return this;
}
이것은 Function.prototype에 공개 메소드를 추가합니다. 클래스에 의해 확장될 수 있으며 모든 기능에서 사용할 수 있습니다. 이름과 함수를 매개변수로 사용합니다.
이것을 반환합니다. 값을 반환하지 않는 메서드를 작성할 때 일반적으로 이를 반환하도록 합니다. 이는 연결된 진술을 형성할 수 있습니다.
다음은 상속 메소드로, 한 클래스가 다른 클래스에서 상속됨을 나타냅니다. 두 클래스를 모두 정의한 후에 정의해야 하지만 메서드 상속 전에 호출해야 합니다.
Function.method('inherits', function (부모) {
var d = 0, p = (this.prototype = new parent())
this.method('uber', function uber(name) {
var f, r, t = d , v = parent.prototype;
if (t) {
while (t) {
v = v.constructor.prototype;
t -= 1; 🎜>f = v[이름];
} else {
f = p[이름]
if (f == this[이름]) {
f = v[이름]; 🎜>}
}
d = 1;
r = f.apply(this, Array.prototype.slice.apply(arguments, [1]))
d -= 1; 🎜>return r;
})
return
});
다음으로 Function 클래스를 확장해 보겠습니다. 상위 클래스의 인스턴스를 추가하고 이를 새 프로토타입으로 만듭니다. 또한 생성자 필드를 수정하고 uber 메소드를 추가해야 합니다.
Uber 메소드는 자체 프로토타입에서 메소드를 찾습니다. 이는 기생 상속 또는 클래스 확장의 경우입니다. 클래스에서 상속하는 경우 부모의 프로토타입에서 함수를 찾아야 합니다. return 문은 함수를 호출하기 위해 함수의 Apply 메서드를 호출하는 동시에 이를 명시적으로 설정하고 매개 변수를 전달합니다. 매개변수(있는 경우)는 인수 배열에서 가져옵니다. 안타깝게도 인수 배열은 실제 배열이 아니므로 배열에서 슬라이스 메서드를 호출하려면 Apply를 다시 사용해야 합니다.
마지막으로 스위스 메소드
함수 .method(' swiss', function (parent) {
for (var i = 1; i < 인수.length; i = 1) {
var name = 인수[i];
this. 프로토타입[이름] = parent.prototype[이름];
return this;
})
스위스 메소드는 각 매개변수를 반복합니다. 각 이름에 대해 부모 프로토타입의 멤버를 새 클래스의 프로토타입에 복사합니다.
요약
JavaScript는 클래스 언어처럼 사용할 수 있지만 매우 독특한 표현 수준을 가지고 있습니다. 우리는 클래스 상속, 스위스 상속, 기생 상속, 클래스 확장, 객체 확장을 살펴보았습니다. 이 일련의 코드 재사용 패턴은 모두 항상 작고 단순한 것으로 간주되었던 이 JavaScript 언어에서 나올 수 있습니다.
클래스 객체는 "하드"입니다. "하드" 개체에 멤버를 추가하는 유일한 방법은 새 클래스를 만드는 것입니다. JavaScript에서 객체는 "소프트"합니다. "소프트" 개체에 멤버를 추가하려면 간단히 값을 할당하면 됩니다.
JavaScript의 클래스는 매우 유연하기 때문에 더 복잡한 클래스 상속을 생각할 수도 있습니다. 그러나 깊은 상속은 적합하지 않습니다. 얕은 상속은 더 효율적이고 표현하기 쉽습니다.