>웹 프론트엔드 >JS 튜토리얼 >JavaScript 객체 지향 - 프로토타입 체인 기반 상속

JavaScript 객체 지향 - 프로토타입 체인 기반 상속

黄舟
黄舟원래의
2017-01-19 15:21:331321검색

객체지향의 특징 중 하나는 상속입니다. 대부분의 객체지향 프로그래밍 언어는 인터페이스 상속과 구현 상속이라는 두 가지 유형의 상속을 지원합니다. 인터페이스 상속은 메서드 시그니처만 상속하는 반면, 구현 상속은 실제 메서드를 상속합니다. JavaScript에는 함수에 서명이 없으므로 인터페이스 상속을 구현할 수 없습니다. JavaScript에서는 상속이 주로 프로토타입 체인을 통해 구현됩니다.

프로토타입 체인을 기반으로 상속 구현

프로토타입 체인을 기반으로 상속을 구현하는 기본 아이디어는 프로토타입을 사용하여 하나의 참조 유형이 다른 참조 유형의 속성과 메서드를 상속하도록 하는 것입니다. 이전에는 프로토타입, 생성자 및 객체 인스턴스 간의 관계를 소개하고 메모리 모델 구조를 자세히 분석했습니다. 다음 예제를 사용하여 JavaScript가 프로토타입 체인을 기반으로 상속을 구현하는 방법을 분석합니다.

// 创建父类
function Parent(){
  this.parentValue = "Parent";
}
// 在父类的原型中添加方法
Parent.prototype.showParentValue = function(){
  alert(this.parentValue);
}
 
// 创建子类
function Child(){
  this.childValue ="Child";
}
// 实现继承,让子类Child的原型链指向Parent对象
Child.prototype = new Parent();
 
// 在子类的原型中添加方法
Child.prototype.showChildValue = function(){
  alert(this.childValue);
}
 
// 创建子类对象
var c = new Child();
 
// 子类对象调用继承自父类的方法
c.showParentValue();
 
// 子类对象调用自己的方法
c.showChildValue();

위 코드에서는 먼저 상위 클래스 Parent를 생성하고 해당 프로토타입에 showParentValue 메서드를 추가합니다.

그런 다음 하위 클래스 Child를 만들고 하위 클래스의 프로토타입 체인이 상위 클래스를 가리키도록 하여 상속을 구현했습니다.

/** 实现继承的关键代码 **/Child.prototype = new Parent();

그런 다음 하위 클래스의 프로토타입 체인에 showChildValue 메서드를 추가합니다. 그러면 하위 클래스 객체가 생성됩니다. 이때 하위 클래스 객체 c는 자신의 메서드나 상위 클래스에서 상속받은 메서드를 호출할 수 있습니다.

위 코드를 실행한 후 하위 클래스와 상위 클래스의 메모리 모델 다이어그램은 다음과 같습니다.

JavaScript 객체 지향 - 프로토타입 체인 기반 상속

위 그림에서 볼 수 있듯이 , 하위 클래스를 생성할 때 하위 클래스의 프로토타입 속성은 하위 클래스의 프로토타입 객체를 가리킵니다. Child.prototype = new Parent() 문을 통해 하위 클래스의 프로토타입이 상위 클래스 객체를 가리키도록 만들면 실제로 하위 클래스의 프로토타입 객체를 다시 작성합니다. 이때 하위 클래스의 프로토타입 객체에는 상위 클래스의 프로토타입 객체를 가리키는 _proto_ 속성이 있게 됩니다. 원래 하위 클래스 프로토타입 객체(그림의 빨간색 영역)는 실제로 쓸모가 없습니다.

하위 클래스 프로토타입에 추가한 메서드는 새 하위 클래스 프로토타입에 추가됩니다. 동시에 상위 클래스의 속성도 새 하위 클래스 프로토타입에 기록됩니다.

하위 클래스 객체 c를 생성한 후 객체 c를 통해 상위 클래스의 showParentValue 메서드를 호출합니다. 객체 c가 자신의 공간에서 이 메서드를 찾지 못하면 _proto_ 속성을 통해 하위 클래스의 프로토타입에서 검색하고 이 메서드를 찾지 못한 다음 _proto_를 통해 상위 클래스의 프로토타입으로 이동합니다. 속성을 검색하면 이때 showParentValue 메소드가 올바르게 발견되고 실행됩니다.

위 과정은 프로토타입 체인을 기반으로 상속을 구현하는 과정입니다.

상속 구현을 위한 프로토타입 체인 기반의 완전한 메모리 모델

위의 상속 구현을 위한 프로토타입 체인 기반의 메모리 모델에서는 실제로 객체가 하나 적습니다: Object. 모든 참조 유형은 Object에서 상속되며 이 상속도 프로토타입 체인을 통해 구현됩니다. 따라서 상속을 구현하기 위한 프로토타입 체인 기반의 완전한 메모리 모델은 다음과 같습니다.

JavaScript 객체 지향 - 프로토타입 체인 기반 상속

모든 함수의 기본 프로토타입은 Object이므로 기본 프로토타입에는 Object.prototype을 가리키는 내부 포인터입니다. Object.prototype에는 hasOwnProperty, isPrototypeOf, propertyEmunerable, toLocaleString, toString 및 valueOf 메소드가 내장되어 있으므로 사용자 정의 유형이 이러한 메소드를 상속합니다.

프로토타입 체인을 상속에 사용할 때 주의할 점

프로토타입 체인을 상속에 사용할 때 다음 사항에 주의하시기 바랍니다.

  • 1. 프로토타입 체인을 설정한 후 프로토타입 체인을 재할당할 수 없습니다.

  • 2. 메소드는 프로토타입 체인에서 할당 후 추가되거나 재정의되어야 합니다.

첫 번째로 다음 예를 살펴보겠습니다.

function Parent(){
  this.parentValue = "Parent";
}
Parent.prototype.showParentValue = function(){
  alert(this.parentValue);
}
function Child(){
  this.childValue ="Child";
}
//实现继承,让Child的原型链指向Parent对象
Child.prototype = new Parent();
 
//下面的操作重写了Child的原型
Child.prototype = {
  showChildValue:function(){
    alert(this.value);
  }
}

在上面的代码中,我们分别创建了一个父类和一个子类,并让子类的原型指向父类对象,实现继承。但是在这之后,我们又通过Child.prototype = {...}的方式重写了子类的原型,在原型的重写一文中我们已经讲过,重写原型实际上是使子类的原型重新指向一个新的子类原型,这个新的子类原型和父类之间并没有任何的关联关系,所以子类和父类之间此时不再存在继承关系。

对于第二点也很好理解,也来看一个例子:

function Parent(){
  this.parentValue = "Parent";
}
Parent.prototype.showParentValue = function(){
  alert(this.parentValue);
}
function Child(){
  this.childValue ="Child";
}
 
//在实现继承之前为子类在原型中添加方法
Child.prototype.showChildValue = function(){
  alert(this.childValue);
}
 
//实现继承,让Child的原型链指向Parent对象
Child.prototype = new Parent();

在上面的代码中,我们分别创建了父类和子类。在创建子类之后马上为子类在原型中添加一个方法,然后才让Child的原型链指向Parent对象,实现继承。

这样做的后果是实现继承之后,子类指向的是新的子类原型,而前面添加的方法是放置在原来的原型中的(内存模型图中的红色区域),所以在实现继承之后,子类对象将不再拥有这个方法,因为原来的原型现在已经没有作用了。

 方法的覆盖及原型链继承的缺点

如果我们需要实现子类的方法来覆盖父类的方法,只需要在子类的原型中添加与父类同名的方法即可。

/** 覆盖父类中的showParentValue方法 **/
Child.prototype.showParentValue = function(){
  alert("Override Parent method");
}

原型链虽然是否强大,可以实现继承,但是原型链也存在一些缺点。原型链继承的缺点主要有:

  • 1、使用原型链进行继承最大的缺点是无法从子类中调用父类的构造函数,这样就没有办法把子类中的属性赋值到父类中。

  • 2、如果父类中存在引用类型的属性,此时这个引用类型会添加到子类的原型中,当第一个对象修改这个引用之后,其它对象的引用同样会被修改。

原型链和原型在处理引用类型的值的时候存在同样的问题。我们在介绍原型的时候曾经举过一个使用引用类型的例子。在使用原型链时同样会有这个问题。来看下面的例子:

// 创建父类
function Parent(){
  this.parentValue = "Parent";
  //引用类型的属性
  this.friends = ['Leon','Ada'];
}
// 在父类的原型中添加方法
Parent.prototype.showParentValue = function(){
  console.info(this.name+"["+this.friends+"]");
}
 
// 创建子类
function Child(){
  this.childValue ="Child";
}
// 实现继承,让子类Child的原型链指向Parent对象
Child.prototype = new Parent();
 
// 在子类的原型中添加方法
Child.prototype.showChildValue = function(){
  console.info(this.name+"["+this.friends+"]");
}
 
// 创建子类对象
var person1 = new Child();
person1.name = "Jack";
person1.friends.push('Tom');
var person2 = new Child();
person2.name = "John";
console.info(person2.friends);

在上面的代码中,在父类中有一个引用类型的数组对象属性friends,在子类实现继承之后,子类对象person1为它的friends添加了一个新的朋友,此时,新的朋友是被添加到父类的原型中的,所以在这之后创建的所有新的对象都会有一个新的朋友“Tom”。这就是引用类型属性存在的问题。

以上就是JavaScript面向对象-基于原型链实现继承的内容,更多相关内容请关注PHP中文网(www.php.cn)!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.