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错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

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

(0)

相关推荐

  • javascript的函数、创建对象、封装、属性和方法、继承

    一,function 从一开始接触到js就感觉好灵活,每个人的写法都不一样,比如一个function就有N种写法 如:function showMsg(){},var showMsg=function(){},showMsg=function(){} 似乎没有什么区别,都是一样的嘛,真的是一样的吗,大家看看下面的例子 复制代码 代码如下: ///----------------------------------------------------------------------------

  • 前端开发必须知道的JS之原型和继承

    一. 原型与构造函数 Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型.这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型.譬如普通函数: 复制代码 代码如下: function F(){ alert(F.prototype instanceof Object) //true; } 构造函数,也即构造对象.首先了解下通过构造函数实例化对象的过程. 复制代码 代码如下: function A(x){ this.x

  • Javascript面向对象编程(二) 构造函数的继承

    今天要介绍的是,如何生成一个"继承"多个对象的实例. 比如,现在有一个"动物"对象的构造函数, 复制代码 代码如下: function Animal(){ this.species = "动物"; } 还有一个"猫"对象的构造函数, 复制代码 代码如下: function Cat(name,color){ this.name = name; this.color = color; } 怎样才能使"猫"继承&

  • Javascript面向对象编程(三) 非构造函数的继承

    今天是最后一个部分,介绍不使用构造函数实现"继承". 一.什么是"非构造函数"的继承? 比如,现在有一个对象,叫做"中国人". 复制代码 代码如下: var Chinese = { nation:'中国' }; 还有一个对象,叫做"医生". 复制代码 代码如下: var Doctor ={ career:'医生' } 请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个&

  • js对象的构造和继承实现代码

    复制代码 代码如下: <script> //定义js的user对象 function User(name,age){ this.name=name, this.age=age, this.getName=function(){ return this.name; }, this.getAge=function(){ return this.age; } } //实例化一个对象 var use=new User("aa",21); alert(use.name); alert

  • JavaScript面向对象之Prototypes和继承

    一.前言 本文翻译自微软的牛人Scott Allen Prototypes and Inheritance in JavaScript ,本文对到底什么是Prototype和为什么通过Prototype能实现继承做了详细的分析和阐述,是理解JS OO 的佳作之一.翻译不好的地方望大家修改补充. 二.正文 JavaScript中的面向对象不同于其他语言,在学习前最好忘掉你所熟知的面向对象的概念.JS中的OO更强大.更值得讨论(arguably).更灵活. 1.类和对象 JS从传统观点来说是面向对象

  • JavaScript的原型继承详解

    JavaScript是一门面向对象的语言.在JavaScript中有一句很经典的话,万物皆对象.既然是面向对象的,那就有面向对象的三大特征:封装.继承.多态.这里讲的是JavaScript的继承,其他两个容后再讲. JavaScript的继承和C++的继承不大一样,C++的继承是基于类的,而JavaScript的继承是基于原型的. 现在问题来了. 原型是什么?原型我们可以参照C++里的类,同样的保存了对象的属性和方法.例如我们写一个简单的对象 复制代码 代码如下: function Animal

  • JS继承--原型链继承和类式继承

    什么是继承啊?答:别人白给你的过程就叫继承. 为什么要用继承呢?答:捡现成的呗. 好吧,既然大家都想捡现成的,那就要学会怎么继承! 在了解之前,你需要先了解构造函数.对象.原型链等概念...... JS里常用的两种继承方式: 原型链继承(对象间的继承)类式继承(构造函数间的继承) 原型链继承: 复制代码 代码如下: //要继承的对象var parent={name : "baba" say : function(){ alert("I am baba");}} //

  • Javascript基于对象三大特性(封装性、继承性、多态性)

    Javascript基于对象的三大特征和C++,Java面向对象的三大特征一样,都是封装(encapsulation).继承(inheritance )和多态(polymorphism ).只不过实现的方式不同,其基本概念是差不多的.其实除三大特征之外,还有一个常见的特征叫做抽象(abstract),这也就是我们在一些书上有时候会看到面向对象四大特征的原因了. 一.封装性     封装就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),

  • javascript 面向对象全新理练之继承与多态

    1 又是几个基本概念 为什么要说又呢? 在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系. 1.1 定义和赋值 变量定义是指用 var a; 这种形式来声明变量. 函数定义是指用 function a(...) {...} 这种形式来声明函数. var a = 1; 是两个过程.第一个过程是定义变量 a,第二个过程是给变量 a 赋值. 同样 var a = function(...) {};

  • javascript 面向对象全新理练之原型继承

    首先创建一个父类的实例化对象,然后将该对象赋给子类的 prototype 属性. 这样,父类中的所有公有实例成员都会被子类继承.并且用 instanceof 运算符判断时,子类的实例化对象既属于子类,也属于父类. 然后将子类本身赋值给它的 prototype 的 constructor 属性.(注意:这里赋值的时候是没有 () 的!) 这一步是为了保证在查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,而不是其父类的定义. 接下来,通过对 o.method1() 调用的

  • javascript 面向对象,实现namespace,class,继承,重载

    由于组里项目大多的javascript,css等客户端工作是另一同事在负责,该同事又特忙无法重构,老大也就只是提建议并未立即实施重构.但是我前些日子也改过些许客户端的小bug,确实那代码看得让人有些云里雾里,不知身在哪山,轻易不敢动代码,于是就自己动手鼓捣起我曾又爱又恨的javascript来,自己写一个简单的js实现namespace,继承,重载等面向对象的特性.欢迎拍砖灌水 .定义namespace Namesapce.js 复制代码 代码如下: Namespace = new Object

  • javascript 面向对象编程基础:继承

    我们看到这里继承的概念是多么的直白,"拷贝一个类的prototype 到另外一个类",好,Code is cheap,看代码: function class1() { } function class2() { } class2.prototype = class1.prototype; class2.moreProperty1 = " class 2 additional string " ; class2.moreMethod1 = function () {

随机推荐