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


Posted in Javascript onApril 05, 2017

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

实际上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语言结构小记(一)
Sep 10 Javascript
深入理解JavaScript系列(9) 根本没有“JSON对象”这回事!
Jan 15 Javascript
解决Extjs4中form表单提交后无法进入success函数问题
Nov 26 Javascript
jquery图片切换实例分析
Apr 15 Javascript
JQuery记住用户名密码实现下次自动登录功能
Apr 27 Javascript
jQ处理xml文件和xml字符串的方法(详解)
Nov 22 Javascript
Jquery获取radio选中的值
May 05 jQuery
基于layPage插件实现两种分页方式浅析
Jul 27 Javascript
VUE实现密码验证与提示功能
Oct 18 Javascript
Vue通过for循环随机生成不同的颜色或随机数的实例
Nov 09 Javascript
Vue管理系统前端之组件拆分封装详解
Aug 23 Javascript
JavaScript中layim之整合右键菜单的示例代码
Feb 06 Javascript
webpack2.0搭建前端项目的教程详解
Apr 05 #Javascript
详解使用fetch发送post请求时的参数处理
Apr 05 #Javascript
详解用webpack2.0构建vue2.0超详细精简版
Apr 05 #Javascript
关于vuex的学习实践笔记
Apr 05 #Javascript
详解基于webpack和vue.js搭建开发环境
Apr 05 #Javascript
ionic2打包android时gradle无法下载的解决方法
Apr 05 #Javascript
使用gulp搭建本地服务器并实现模拟ajax
Apr 05 #Javascript
You might like
第1次亲密接触PHP5(1)
2006/10/09 PHP
深入分析php之面向对象
2013/05/15 PHP
如何使用PHP实现javascript的escape和unescape函数
2013/06/29 PHP
php使用Jpgraph创建折线图效果示例
2017/02/15 PHP
Yii框架的布局文件实例分析
2019/09/04 PHP
限制文本字节数js代码
2007/03/06 Javascript
[Web]防止用户复制页面内容和另存页面的方法
2009/02/06 Javascript
jquery1.4.2 for Visual studio 2010 模板文件
2010/07/14 Javascript
{}与function(){}选用空对象{}来存放keyValue
2012/05/23 Javascript
TextArea设置MaxLength属性最大输入值的js代码
2012/12/21 Javascript
文本框文本自动补全效果示例分享
2014/01/19 Javascript
js利用事件的阻止冒泡实现点击空白模态框的隐藏
2014/01/24 Javascript
JavaScript strike方法入门实例(给字符串加上删除线)
2014/10/17 Javascript
js记录点击某个按钮的次数-刷新次数为初始状态的实例
2017/02/15 Javascript
JavaScript实现的商品抢购倒计时功能示例
2017/04/17 Javascript
解析vue data不可以使用箭头函数问题
2018/07/03 Javascript
解决Layui当中的导航条动态添加后渲染失败的问题
2019/09/25 Javascript
vue.js的状态管理vuex中store的使用详解
2019/11/08 Javascript
ES6对象操作实例详解
2020/05/23 Javascript
[36:54]Mineski vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
Django在Win7下的安装及创建项目hello word简明教程
2014/07/14 Python
Python pickle模块用法实例分析
2015/05/27 Python
python3之微信文章爬虫实例讲解
2017/07/12 Python
Python网络爬虫与信息提取(实例讲解)
2017/08/29 Python
python基础教程项目二之画幅好画
2018/04/02 Python
python实现飞机大战
2018/09/11 Python
Python装饰器限制函数运行时间超时则退出执行
2019/04/09 Python
树莓派用python中的OpenCV输出USB摄像头画面
2019/06/22 Python
Python requests及aiohttp速度对比代码实例
2020/07/16 Python
生物科学专业自荐书
2014/06/20 职场文书
以幸福为主题的活动方案
2014/08/22 职场文书
公司演讲稿开场白
2014/08/25 职场文书
购房协议书范本
2014/10/02 职场文书
独生子女证明范本
2015/06/19 职场文书
求职信:会计求职的写作技巧
2019/04/24 职场文书
Python并发编程实例教程之线程的玩法
2021/06/20 Python