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 相关文章推荐
arguments对象
Nov 20 Javascript
给Flash加一个超链接(推荐使用透明层)兼容主流浏览器
Jun 09 Javascript
node.js中的path.normalize方法使用说明
Dec 08 Javascript
客户端验证用户名和密码的方法详解
Jun 16 Javascript
图解prototype、proto和constructor的三角关系
Jul 31 Javascript
利用jQuery来动态为属性添加或者删除属性的简单方法
Dec 02 Javascript
基于jQuery实现火焰灯效果导航菜单
Jan 04 Javascript
JS中的作用域链
Mar 01 Javascript
JS排序之快速排序详解
Apr 08 Javascript
解决Vue页面固定滚动位置的处理办法
Jul 13 Javascript
JS实现页面打印(整体、局部)
Aug 18 Javascript
解决vue select当前value没有更新到vue对象属性的问题
Aug 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下删除一篇文章生成的多个静态页面
2010/08/08 PHP
关于PHP中Object对象的笔记分享
2011/06/28 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
2017/06/02 PHP
PHP微信公众号开发之微信红包实现方法分析
2017/07/14 PHP
有趣的javascript数组定义方法
2010/09/10 Javascript
javascript中关于break,continue的特殊用法与介绍
2012/05/24 Javascript
JS/jQuery实现默认显示部分文字点击按钮显示全部内容
2013/05/13 Javascript
jquery跟js初始化加载的多种方法及区别介绍
2014/04/02 Javascript
Backbone.js 0.9.2 源码注释中文翻译版
2015/06/25 Javascript
jQuery on()绑定动态元素出现的问题小结
2016/02/19 Javascript
把json格式的字符串转换成javascript对象或数组的方法总结
2016/11/03 Javascript
jQuery排序插件tableSorter使用方法
2017/02/10 Javascript
npm 常用命令详解(小结)
2019/01/17 Javascript
微信小程序地图导航功能实现完整源代码附效果图(推荐)
2019/04/28 Javascript
uni-app自定义导航栏按钮|uniapp仿微信顶部导航条功能
2019/11/12 Javascript
微信小程序实现树莓派(raspberry pi)小车控制
2020/02/12 Javascript
[57:38]2018DOTA2亚洲邀请赛3月30日 小组赛A组 OpTic VS OG
2018/03/31 DOTA
Python 检查数组元素是否存在类似PHP isset()方法
2014/10/14 Python
Python学习思维导图(必看篇)
2017/06/26 Python
python给微信好友定时推送消息的示例
2019/02/20 Python
Python使用正则表达式分割字符串的实现方法
2019/07/16 Python
一篇文章弄懂Python中的可迭代对象、迭代器和生成器
2019/08/12 Python
python实现差分隐私Laplace机制详解
2019/11/25 Python
Pytoch之torchvision.transforms图像变换实例
2019/12/30 Python
Pytorch模型转onnx模型实例
2020/01/15 Python
印度最大的旅游网站:MakeMyTrip
2016/10/05 全球购物
值类型与引用类型有什么不同?请举例说明?并分别列举几种相应的数据类型
2015/10/24 面试题
国贸专业个人求职信范文
2014/01/08 职场文书
新闻专业学生的自我评价
2014/02/13 职场文书
大学生党员自我评价范文
2014/04/09 职场文书
仓库规划计划书
2014/04/28 职场文书
2015年医院科室工作总结范文
2015/05/26 职场文书
禁毒心得体会范文
2016/01/15 职场文书
apache基于端口创建虚拟主机的示例
2021/04/22 Servers
为什么node.js不适合大型项目
2021/04/28 Javascript
详解Go语言Slice作为函数参数的使用
2021/07/02 Golang