今天要介绍的是,对象之间的"继承"的五种方法。

比如,现在有一个"动物"对象的构造函数。

  function Animal(){

    this.species = "动物";

  }

还有一个"猫"对象的构造函数。

  function Cat(name,color){

    this.name = name;

    this.color = color;

  }

怎样才能使"猫"继承"动物"呢?

一、 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

  function Cat(name,color){

    Animal.apply(this, arguments);

    this.name = name;

    this.color = color;

  }

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

二、 prototype模式

第二种方法更常见,使用prototype属性。

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

  Cat.prototype = new Animal();

  Cat.prototype.constructor = Cat;

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。

  Cat.prototype = new Animal();

它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?

  Cat.prototype.constructor = Cat;

原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。

  alert(Cat.prototype.constructor == Animal); //true

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。

  alert(cat1.constructor == Cat.prototype.constructor); // true

因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!

  alert(cat1.constructor == Animal); // true

这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。

这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,

  o.prototype = {};

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

  o.prototype.constructor = o;

三、 直接继承prototype

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

现在,我们先将Animal对象改写:

  function Animal(){ }

  Animal.prototype.species = "动物";

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

  Cat.prototype = Animal.prototype;

  Cat.prototype.constructor = Cat;

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

所以,上面这一段代码其实是有问题的。请看第二行

  Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

  alert(Animal.prototype.constructor); // Cat

四、 利用空对象作为中介

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

  var F = function(){};

  F.prototype = Animal.prototype;

  Cat.prototype = new F();

  Cat.prototype.constructor = Cat;

F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。

  alert(Animal.prototype.constructor); // Animal

我们将上面的方法,封装成一个函数,便于使用。

  function extend(Child, Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;

    Child.prototype = new F();

    Child.prototype.constructor = Child;

    Child.uber = Parent.prototype;

  }

使用的时候,方法如下

  extend(Cat,Animal);

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

这个extend函数,就是YUI库如何实现继承的方法。

另外,说明一点,函数体最后一行

  Child.uber = Parent.prototype;

意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

五、 拷贝继承

上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。

首先,还是把Animal的所有不变属性,都放到它的prototype对象上。

  function Animal(){}

  Animal.prototype.species = "动物";

然后,再写一个函数,实现属性拷贝的目的。

  function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;

  }

这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

使用的时候,这样写:

  extend2(Cat, Animal);

  var cat1 = new Cat("大毛","黄色");

Javascript面向对象编程(二):构造函数的继承 by 阮一峰的更多相关文章

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

    Javascript面向对象编程:构造函数的继承   这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承 ...

  2. Javascript面向对象编程:构造函数的继承

    今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...

  3. Javascript面向对象编程(三):非构造函数的继承 by 阮一峰

    今天是最后一个部分,介绍不使用构造函数实现"继承". 一.什么是"非构造函数"的继承? 比如,现在有一个对象,叫做"中国人". var Ch ...

  4. javascript面向对象(三):非构造函数的继承

    本文来自阮一峰 这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承". 今天是最后一个部分,介绍不使用构造函数实现"继承" ...

  5. Javascript异步编程的4种方法(阮一峰)

    转载: http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html 你可能知道,Javascript语言的执 ...

  6. JavaScript面向对象编程学习笔记

    1  Javascript 面向对象编程 所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量.对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例 ...

  7. Javascript面向对象编程(二):构造函数的继承 作者:yuan一峰

    Javascript面向对象编程(二):构造函数的继承   作者: 阮一峰 日期: 2010年5月23日 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生 ...

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

    Javascript面向对象编程:非构造函数的继承   这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承". 今天是最后一个部分,介绍不使 ...

  9. Javascript面向对象编程(三):非构造函数的继承(对象的深拷贝与浅拷贝)

    Javascript面向对象编程(三):非构造函数的继承   作者: 阮一峰 日期: 2010年5月24日 这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现&quo ...

随机推荐

  1. Java NIO 学习笔记

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/3344148.html ...

  2. HTML5之填写个人信息

  3. sql 减去分钟

    SQL SERVER:SELECT DATEADD( minute,-10,GETDATE()) ORACLE:SELECT to_char(sysdate -interval '10' minute ...

  4. SQLite 入门教程(三)好多约束 Constraints

    一.约束 Constraints 在上一篇随笔的结尾,我提到了约束, 但是在那里我把它翻译成了限定符,不太准确,这里先更正一下,应该翻译成约束更贴切一点. 那么什么是约束呢? 我们在数据库中存储数据的 ...

  5. What and where are the stack and heap?

    The stack is the memory set aside as scratch space for a thread of execution. When a function is cal ...

  6. 限制UITextField/UITextView的输入字数与中文输入之后的英文换行问题

    要限制一个UITextField/UITextView的输入字数,首先想到的应该是通过UITextFieldDelegate/UITextViewDelegate的代理方法来限制,那么如何来更好的限制 ...

  7. jq 图片裁剪

    1.html <div class="jcropbox" style="display: none"> <img src="&quo ...

  8. 【转】jquery两稳定版本比较~~

    博客分类: Web前端 jquery  jquery历经了多个版本的更新,版本上的比较貌似没什么必要性,一般来说新的版本会比旧的版本各方面都略有提升,但由于新版中增加了各种新的功能,难免会引起bug的 ...

  9. Vijos1834 NOI2005 瑰丽华尔兹 动态规划 单调双端队列优化

    设dp[t][x][y]表示处理完前t个时间段,钢琴停留在(x,y)处,最多可以走多少个格子 转移时只需逆着当前倾斜的方向统计len个格子(len为时间区间的长度,len=t-s+1),如果遇到障碍就 ...

  10. linuxC编程实战 my_server.c例子问题总结

    今天看linux C 编程实战的my_server例子时,敲到这段代码,对其父子进程关闭socket 进行close调用产生疑问 如图中标注的三个close socket,思考子进程通信结束 关闭自己 ...