登录  /  注册
首页 > web前端 > js教程 > 正文

JavaScript面向对象-基于原型链实现继承

黄舟
发布: 2017-01-19 15:21:33
原创
1135人浏览过

面向对象的特征之一就是继承。大多数面向对象的编程语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于在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既可以调用自己的方法,也可以调用继承自父类的方法。

在执行上面的代码之后,子类和父类的内存模型图如下图所示:

433.jpg

从上图我们可以看到,在创建子类的时候,子类的prototype属性是指向子类的原型对象的。当我们通过Child.prototype = new Parent();语句让子类的原型指向父类对象的时候,实际上是重写了子类的原型对象。此时在子类的原型对象中会有一个_proto_属性指向父类的原型对象。而原来的子类原型对象(图中红色区域)实际上已经没有用了。

之后我们在子类原型中添加的方法会被添加到新的子类原型中。同时,父类中的属性也会被写入到新的子类原型中。

当我们创建了子类对象c之后,通过对象c调用了父类的showParentValue方法。对象c在自己的空间中没有找到这个方法,就会通过_proto_属性去子类的原型中查找,同样也没有找到这个方法,接着它有通过原型中的_proto_属性到父类的原型中去查找,这时,showParentValue方法被找到,并被正确的执行。

上面的过程就是基于原型链实现继承的过程。

完整的基于原型链实现继承的内存模型

在上面的基于原型链实现继承的内存模型中,我们实际上还少描述了一个对象:Object。所有的引用类型都是继承自Object,这个继承也是通过原型链来实现的。因此,完整的基于原型链实现继承的内存模型如下图所示:

434.jpg

所有函数的默认原型都是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)!

智能AI问答
PHP中文网智能助手能迅速回答你的编程问题,提供实时的代码和解决方案,帮助你解决各种难题。不仅如此,它还能提供编程资源和学习指导,帮助你快速提升编程技能。无论你是初学者还是专业人士,AI智能助手都能成为你的可靠助手,助力你在编程领域取得更大的成就。
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
关于CSS思维导图的课件在哪? 课件
凡人来自于2024-04-16 10:10:18
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2024 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号