JavaScript使用原型和原型链实现对象继承的方法详解

发布时间 - 2026-01-11 00:30:59    点击率:

本文实例讲述了JavaScript使用原型和原型链实现对象继承的方法。分享给大家供大家参考,具体如下:

实际上JavaScript并不是一门面向对象的语言,不过JavaScript基于原型链的继承方式、函数式语法,使得编程相当灵活,所以可以利用原型链来实现面向对象的编程。

之前对JavaScript一直都是一知半解,这两天看了一下原型链这一块知识,综合练习了一下JavaScript的对象继承方式。

以下就是原型链和原型的关系,引用网上的一张图

在Javascript中,每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个proto指向自己的原型,这样逐层深入直到Object对象的原型,这样就形成了原型链。

- 基本继承模式

function FatherClass() {
  this.type = 'father';
}
FatherClass.prototype.getTyep = function() {
  console.log(this.type);
}
FatherClass.prototype.obj = {age: 35};
function ChildClass() {
  this.type = 'child';
}
ChildClass.prototype = FatherClass();
ChildClass.prototype.getType = function() {
  console.log(this.type);
}
var father = new FatherClass();
var child = new ChildClass();
father.getTyep();
child.getType();

此方法有优点也有缺点,继承的实现很简单,代码简单容易理解,但是子类继承父类的成员变量需要自己重新初始化,相当于父类有多少个成员变量,在子类中还需要重新定义及初始化

function FatherClass(type) {
  this.type = type || 'father';
}
function ChildClass(type) {
  this.type = type || 'child';
}
ChildClass.prototype = FatherClass();
ChildClass.prototype.getType = function() {
  console.log(this.type);
}
var father = new FatherClass('fatClass');
var child = new ChildClass('chilClass');

上面这种情况还只是需要初始化name属性,如果初始化工作不断增加,这种方式是很不方便的。因此就有了下面一种改进的方式。

- 借用构造函数

var Parent = function(name){
  this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
  return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
  Parent.apply(this,arguments) ;
} ;
Child.prototype = Parent.prototype ;
var parent = new Parent('myParent') ;
var child = new Child('myChild') ;
console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

这样我们就只需要在子类构造函数中执行一次父类的构造函数,同时又可以继承父类原型中的属性,这也比较符合原型的初衷,就是把需要复用的内容放在原型中,我们也只是继承了原型中可复用的内容。

- 临时构造函数模式(圣杯模式)

上面借用构造函数模式最后改进的版本还是存在问题,它把父类的原型直接赋值给子类的原型,这就会造成一个问题,就是如果对子类的原型做了修改,那么这个修改同时也会影响到父类的原型,进而影响父类对象,这个肯定不是大家所希望看到的。为了解决这个问题就有了临时构造函数模式。

var Parent = function(name){
  this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
  return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
  Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;
var parent = new Parent('myParent') ;
var child = new Child('myChild') ;
console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

个人综合模式

Javascript模式》中到圣杯模式就结束了,可是不管上面哪一种方法都有一个不容易被发现的问题。大家可以看到我在'Parent'的prototype属性中加入了一个obj对象字面量属性,但是一直都没有用。我们在圣杯模式的基础上来看看下面这种情况:

var Parent = function(name){
  this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
  return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
  Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;
var parent = new Parent('myParent') ;
var child = new Child('myChild') ;
console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

在上面这种情况中,当我修改child对象obj.a的时候,同时父类的原型中的obj.a也会被修改,这就发生了和共享原型同样的问题。出现这个情况是因为当访问child.obj.a的时候,我们会沿着原型链一直找到父类的prototype中,然后找到了obj属性,然后对obj.a进行修改。再看看下面这种情况:

var Parent = function(name){
  this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
  return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
  Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;
var parent = new Parent('myParent') ;
var child = new Child('myChild') ;
console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

这里有一个关键的问题,当对象访问原型中的属性时,原型中的属性对于对象来说是只读的,也就是说child对象可以读取obj对象,但是无法修改原型中obj对象引用,所以当child修改obj的时候并不会对原型中的obj产生影响,它只是在自身对象添加了一个obj属性,覆盖了父类原型中的obj属性。而当child对象修改obj.a时,它先读取了原型中obj的引用,这时候child.obj和Parent.prototype.obj是指向同一个对象的,所以child对obj.a的修改会影响到Parent.prototype.obj.a的值,进而影响父类的对象。AngularJS中关于$scope嵌套的继承方式就是模范Javasript中的原型继承来实现的。

根据上面的描述,只要子类对象中访问到的原型跟父类原型是同一个对象,那么就会出现上面这种情况,所以我们可以对父类原型进行拷贝然后再赋值给子类原型,这样当子类修改原型中的属性时就只是修改父类原型的一个拷贝,并不会影响到父类原型。具体实现如下:

var deepClone = function(source,target){
  source = source || {} ;
  target = target || {};
  var toStr = Object.prototype.toString ,
    arrStr = '[object array]' ;
  for(var i in source){
    if(source.hasOwnProperty(i)){
      var item = source[i] ;
      if(typeof item === 'object'){
        target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ;
        deepClone(item,target[i]) ;
      }else{
        target[i] = item;
      }
    }
  }
  return target ;
} ;
var Parent = function(name){
  this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
  return this.name ;
} ;
Parent.prototype.obj = {a : '1'} ;
var Child = function(name){
  Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;
var child = new Child('child') ;
var parent = new Parent('parent') ;
console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = '2' ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //1

综合上面所有的考虑,Javascript继承的具体实现如下,这里只考虑了Child和Parent都是函数的情况下:

var deepClone = function(source,target){
  source = source || {} ;
  target = target || {};
  var toStr = Object.prototype.toString ,
    arrStr = '[object array]' ;
  for(var i in source){
    if(source.hasOwnProperty(i)){
      var item = source[i] ;
      if(typeof item === 'object'){
        target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ;
        deepClone(item,target[i]) ;
      }else{
        target[i] = item;
      }
    }
  }
  return target ;
} ;
var extend = function(Parent,Child){
  Child = Child || function(){} ;
  if(Parent === undefined)
    return Child ;
  //借用父类构造函数
  Child = function(){
    Parent.apply(this,argument) ;
  } ;
  //通过深拷贝继承父类原型
  Child.prototype = deepClone(Parent.prototype) ;
  //重置constructor属性
  Child.prototype.constructor = Child ;
} ;

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。


# JavaScript  # 原型  # 原型链  # 对象  # 继承  # JavaScript面向对象继承原理与实现方法分析  # JavaScript中的对象继承关系  # 理解javascript对象继承  # 理解js对象继承的N种模式  # js对象继承之原型链继承实例  # Javascript中对象继承的实现小例  # javaScript面向对象继承方法经典实现  # javascript 面向对象继承  # 深度解析JavaScript对象继承  # 子类  # 这种情况  # 也会  # 都是  # 影响到  # 都有  # 也有  # 面向对象  # 圣杯  # 来实现  # 有一个  # 自己的  # 复用  # 是一个  # 这一  # 就会  # 是在  # 是因为  # 我在  # 放在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何在VPS电脑上快速搭建网站?  javascript中的try catch异常捕获机制用法分析  简历没回改:利用AI润色让你的文字更专业  如何用VPS主机快速搭建个人网站?  php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】  Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】  EditPlus中的正则表达式 实战(4)  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  IOS倒计时设置UIButton标题title的抖动问题  Laravel怎么连接多个数据库_Laravel多数据库连接配置  Laravel怎么使用Collection集合方法_Laravel数组操作高级函数pluck与map【手册】  Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】  Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中  详解Android中Activity的四大启动模式实验简述  Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程  Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤  高防服务器租用首荐平台,企业级优惠套餐快速部署  Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】  今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】  如何快速完成中国万网建站详细流程?  如何制作公司的网站链接,公司想做一个网站,一般需要花多少钱?  如何快速生成可下载的建站源码工具?  如何在 React 中条件性地遍历数组并渲染元素  Python自动化办公教程_ExcelWordPDF批量处理案例  Laravel如何保护应用免受CSRF攻击?(原理和示例)  Laravel集合Collection怎么用_Laravel集合常用函数详解  Laravel如何处理表单验证?(Requests代码示例)  如何获取免费开源的自助建站系统源码?  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  Laravel如何生成URL和重定向?(路由助手函数)  网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?  美食网站链接制作教程视频,哪个教做美食的网站比较专业点?  ,怎么在广州志愿者网站注册?  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  LinuxShell函数封装方法_脚本复用设计思路【教程】  详解Android图表 MPAndroidChart折线图  Laravel如何配置Horizon来管理队列?(安装和使用)  宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程  Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  Laravel队列由Redis驱动怎么配置_Laravel Redis队列使用教程  深圳网站制作的公司有哪些,dido官方网站?  大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?  深圳网站制作培训,深圳哪些招聘网站比较好?  Laravel如何实现API速率限制?(Rate Limiting教程)  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】  Laravel如何使用缓存系统提升性能_Laravel缓存驱动和应用优化方案  实现点击下箭头变上箭头来回切换的两种方法【推荐】  如何基于云服务器快速搭建个人网站?  ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法