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 类
Nov 07 Javascript
jQuery .attr()和.removeAttr()方法操作元素属性示例
Jul 16 Javascript
jQuery中DOM树操作之复制元素的方法
Jan 23 Javascript
javascript精确统计网站访问量实例代码
Dec 19 Javascript
jquery结合html实现中英文页面切换
Nov 29 Javascript
JS HTML图片显示Canvas 压缩功能
Jul 21 Javascript
浅谈vuex之mutation和action的基本使用
Aug 29 Javascript
vue实现某元素吸顶或固定位置显示(监听滚动事件)
Dec 13 Javascript
Vue自定义指令封装节流函数的方法示例
Jul 09 Javascript
深入浅析Vue中mixin和extend的区别和使用场景
Aug 01 Javascript
wx-charts 微信小程序图表插件的具体使用
Aug 18 Javascript
微信小程序实现电影App导航和轮播
Nov 30 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
?算你??的 PHP 程式大小
2006/12/06 PHP
php根据某字段对多维数组进行排序的方法
2015/03/07 PHP
老生常谈php 正则中的i,m,s,x,e分别表示什么
2017/03/02 PHP
PHP实现的字符串匹配算法示例【sunday算法】
2017/12/19 PHP
laravel Task Scheduling(任务调度)在windows下的使用详解
2019/10/22 PHP
用正则获取指定路径文件的名称
2007/02/27 Javascript
JavaScript 高效运行代码分析
2010/03/18 Javascript
JavaScript操纵窗口的方法小结
2013/06/28 Javascript
深入理解jquery中的事件与动画
2016/05/24 Javascript
js操作XML文件的实现方法兼容IE与FireFox
2016/06/25 Javascript
nodejs实例解析(输出hello world)
2017/01/03 NodeJs
Vue 表情包输入组件的实现代码
2019/01/21 Javascript
js prototype深入理解及应用实例分析
2019/11/25 Javascript
JS绘图Flot如何实现可选显示曲线图功能
2020/10/16 Javascript
python thread 并发且顺序运行示例
2009/04/09 Python
Python中对象迭代与反迭代的技巧总结
2016/09/17 Python
python Socket之客户端和服务端握手详解
2017/09/18 Python
python 高效去重复 支持GB级别大文件的示例代码
2018/11/08 Python
Python使用combinations实现排列组合的方法
2018/11/13 Python
Python面向对象类编写细节分析【类,方法,继承,超类,接口等】
2019/01/05 Python
python接口自动化(十七)--Json 数据处理---一次爬坑记(详解)
2019/04/18 Python
python文档字符串(函数使用说明)使用详解
2019/07/30 Python
画pytorch模型图,以及参数计算的方法
2019/08/17 Python
python能开发游戏吗
2020/06/11 Python
英国知名的护肤彩妆与时尚配饰大型综合零售电商:Unineed
2016/11/21 全球购物
PHP如何与mysql建立链接
2013/05/05 面试题
Tomcat Mysql datasource数据源配置
2015/12/28 面试题
什么是命名空间(NameSpace)
2015/11/24 面试题
毕业生的自我鉴定
2013/10/29 职场文书
2014小学教师年度考核工作总结
2014/12/03 职场文书
火烧圆明园观后感
2015/06/03 职场文书
同意报考证明
2015/06/17 职场文书
关于国庆节的广播稿
2015/08/19 职场文书
浅谈MySQL next-key lock 加锁范围
2021/06/07 MySQL
Python实现简单的猜单词
2021/06/15 Python
为什么MySQL 删除表数据 磁盘空间还一直被占用
2021/10/16 MySQL