要彻底弄明白js中的继承,我们首先要弄清楚js中的一个很重要的概念那就是原型链。

1.什么是原型链?

我们知道每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如果,让原型对象等于另一个引用类型的实例,那么原型对象中将包含一个指向另一个原型的指针,相应地,另一个原型对象中包含着一个指向另一个构造函数的指针。假如另一个原型对象又是另一个引用类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型之间的链条。这就是所谓原型链的基本概念。关键就是让原型等于另一个引用类型的实例。

2.默认原型

我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链来完成的。记住:所有函数的默认原型都是Object的实例,因此默认原型都包含一个内部指针,指向Object.prototype.这也是所有自定义类型都会继承toString(),toValue()等方法的根本原因。

3.确定原型和实例之间的关系

1.使用instanceof操作符,只要用这个操作符来检测实例与原型链中出现过的构造函数,结果就会返回true

2.使用isPrototypeOf(),只要是原型链中出现过的原型,都可以说是原型链中所派生出来的实例的原型,结果就会返回true。

4.谨慎地定义方法

子类型有时候需要覆盖超类中的某个方法,或者需要添加超类中不存在的某个方法。但不管怎样,给原型添加的方法一定要放在替换原型的语句之后,在通过原型实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型,切断实例与原型之间的连接关系。

5.原型链的问题

引用类型值的原型属性会被实例共享;而这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型实现继承的时候,原型实际上是变成另一个类型的实例,于是,原先的实例属性顺理成章地变成了现在的原型属性了。

下面代码可以说明这个问题:

  1. function SuperType() {
  2. this.colors = ['red', 'orange', 'green'];
  3. }
  4. function SubType() {
  5.  
  6. }
  7. // 继承Supertype
  8. SubType.prototype = new SuperType();
  9. var instance1 = new SubType();
  10. instance1.colors.push('black');
  11. console.log(instance1.colors); // ['red','orange','green','black']
  12. var instance2 = new SubType();
  13. console.log(instance2.colors); // ['red','orange','green',‘black’]

在这里我们把SubType的原型指向SuperType的一个实例,由于SuperType只实例了一次,所以实例instance1和instance2共享了colors属性,导致一变多变的现象出现,我们想的是每一个实例都有属于自己的一个属性副本,并不会进行相互的干扰。

此外,在创建子类型的实例的时候,我们无法向超类型的构造函数传递参数。

6.借用构造函数

这种技术的思想相当简单,即在子类型构造函数的内部调用超类型的构造函数,函数只不过是在特定环境中执行代码的对象,因此通过apply,call等方法也可以执行函数。

  1. function SuperType() {
  2. this.colors = ['red', 'orange', 'green'];
  3. }
  4. function SubType() {
  5. // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本
  6. SuperType.call(this)
  7. }
  8. // 继承Supertype
  9. SubType.prototype = new SuperType();
  10. var instance1 = new SubType();
  11. instance1.colors.push('black');
  12. console.log(instance1.colors); // ['red','orange','green','black']
  13. var instance2 = new SubType();
  14. console.log(instance2.colors); // ['red','orange','green']

如上面代码所示,我们在子类型的构造函数中执行了超类型的构造函数,这样子就为SubType的每一个实例单独创建了一个colors的属性副本,做到不相互干扰。

别忘了,apply个call函数除了执行函数之外,还可以传递参数,那么我们修改上面的例子,就可以实现向超类型的构造函数传递初始化参数。

代码如下:

  1. function SuperType(name) {
  2. this.name = name
  3. }
  4. function SubType(name) {
  5. // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本
  6. SuperType.call(this,name);
  7. this.colors = ['red'];
  8. }
  9. // 继承Supertype
  10. SubType.prototype = new SuperType();
  11. var instance1 = new SubType('instance1');
  12. instance1.colors.push('black');
  13. console.log(instance1.name);// instance1
  14. console.log(instance1.colors); // ['red']
  15. var instance2 = new SubType('instance2');
  16. console.log(instance2.colors); // ['red']
  17. console.log(instance2.name);// instance2

借用构造函数模式的问题,方法都在构造函数中定义,因此函数复用就无从谈起了,而且在超类型的原型中定义的方法,对子类型是不可见的,结果所有类型只能使用构造函数模式。

7.组合继承

组合继承,有时候叫做伪经典继承,指的是将原型链和借用构造函数的技术组合在一起的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。

看如下代码:

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = ['red','orange','green'];
  4. }
  5. SuperType.prototype.sayName = function() {
  6. console.log(this.name);
  7. }
  8. function SubType(name,age) {
  9. // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本
  10. SuperType.call(this,name);
  11. this.age = age;
  12. }
  13. // 继承Supertype
  14. SubType.prototype = new SuperType();
  15. SubType.prototype.constructor = SubType;
  16. SubType.prototype.sayAge = function() {
  17. console.log(this.age);
  18. }
  19. var instance1 = new SubType('instance1',22);
  20. instance1.colors.push('black');
  21. instance1.sayName();// instance1
  22. instance1.sayAge();//
  23. console.log(instance1.colors); // ['red','orange','green','black']
  24. var instance2 = new SubType('instance2',23);
  25. console.log(instance2.colors); // ['red','orange','green']
  26. instance2.sayName();// instance2
  27. instance2.sayAge();//

这样一来,就可以让两个不同的SubType实例既分别拥有自己属性--包括colors属性,又可以使用相同的方法了。

组合继承避免了原型链和构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。而且instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。

8.原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

代码如下:

  1. function object(o) {
  2. function F() { }
  3. F.prototype = o;
  4. return new F();
  5. }

这种方式必须有一个对象可以作为另一个对象原型的基础,如果有这么一个对象,就可以传递给object函数,然后在根据具体需求对得到的对象加以修改即可。

ECMAScript5中新增了Object.create()方法规范了原型式继承。接受两个参数:

1.一个用做新对象原型的对象([[prototype]]__proto__可选)

2.第二个参数是一个用作新对象自定义属性的对象(同名属性会覆盖原型对象上的同名属性)。

在没有必要创建构造函数的时候,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。但是,包含引用类型值的属性始终都会共享相应的值。

9.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。如下代码所示:

  1. function object(o) {
  2. function F() { }
  3. F.prototype = o;
  4. return new F();
  5. }
  6. function createAnother(original) {
  7. var cloen = object(original);
  8. cloen.sayHi = function () {
  9. console.log('hi');
  10. }
  11. return cloen;
  12. }

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点于构造函数模式类似。

10.寄生组合式继承

前面说到的组合继承也有自己的不足,就是会调用两次超类型的构造函数,这样子就给子类型构造函数的原型对象上添加了额外的属性和方法。

代码如下:

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = ['red', 'orange', 'green'];
  4. }
  5. SuperType.prototype.sayName = function () {
  6. console.log(this.name);
  7. }
  8. function SubType(name, age) {
  9. // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本
  10. SuperType.call(this, name); //第二次调用
  11. this.age = age;
  12. }
  13. // 继承Supertype
  14. SubType.prototype = new SuperType(); // 第一次调用
  15. SubType.prototype.constructor = SubType;
  16. SubType.prototype.sayAge = function () {
  17. console.log(this.age);
  18. }
  19. var instance1 = new SubType('instance1', 22);
  20. instance1.colors.push('black');
  21. instance1.sayName();// instance1
  22. instance1.sayAge();//
  23. console.log(instance1.colors); // ['red','orange','green','black']
  24. var instance2 = new SubType('instance2', 23);
  25. console.log(instance2.colors); // ['red','orange','green']
  26. instance2.sayName();// instance2
  27. instance2.sayAge();//

使用寄生组合继承可以这样子写:

  1. function object(o) {
  2. function F() { }
  3. F.prototype = o;
  4. return new F();
  5. }
  6. function inheritPrototype(subType,superType) {
  7. // 返回一个新对象
  8. var prototype = object(superType.prototype);
  9. // 原型中constructor的指向
  10. prototype.constructor = subType;
  11. // 重写原型
  12. subType.prototype = prototype;
  13. }

这样子就可以替换为子类原型赋值的语句了,如下代码:

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = ['red', 'orange', 'green'];
  4. }
  5. SuperType.prototype.sayName = function () {
  6. console.log(this.name);
  7. }
  8. function SubType(name, age) {
  9. // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本
  10. SuperType.call(this, name);
  11. this.age = age;
  12. }
  13. // 继承Supertype
  14. inheritPrototype(SubType,SuperType);
  15. SubType.prototype.constructor = SubType;
  16. SubType.prototype.sayAge = function () {
  17. console.log(this.age);
  18. }
  19. var instance1 = new SubType('instance1', 22);
  20. instance1.colors.push('black');
  21. instance1.sayName();// instance1
  22. instance1.sayAge();//
  23. console.log(instance1.colors); // ['red','orange','green','black']
  24. var instance2 = new SubType('instance2', 23);
  25. console.log(instance2.colors); // ['red','orange','green']
  26. instance2.sayName();// instance2
  27. instance2.sayAge();//
  28.  
  29. function object(o) {
  30. function F() { }
  31. F.prototype = o;
  32. return new F();
  33. }
  34. function inheritPrototype(subType,superType) {
  35. // 返回一个新对象
  36. var prototype = object(superType.prototype);
  37. // 原型中constructor的指向
  38. prototype.constructor = subType;
  39. // 重写原型
  40. subType.prototype = prototype;
  41. }

这样子一来,我们就省略了给子类型原型赋值的操作,避免了给子类原型上面添加了额外的属性。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

从头认识js-js中的继承的更多相关文章

  1. JavaScript 对象的原型扩展(JS面向对象中的继承)

    <script type="text/javascript"> function person(name, age) { this._name = name; this ...

  2. JS创建对象、继承原型、ES6中class继承

    面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...

  3. js中实现继承的几种方式

    首先我们了解,js中的继承是主要是由原型链实现的.那么什么是原型链呢? 由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的 ...

  4. js oop中的三种继承方法

    JS OOP 中的三种继承方法: 很多读者关于js opp的继承比较模糊,本文总结了oop中的三种继承方法,以助于读者进行区分. <继承使用一个子类继承另一个父类,子类可以自动拥有父类的属性和方 ...

  5. 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承

    ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...

  6. JS中的继承(上)

    JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...

  7. JS中的继承(下)

    JS中的继承(下) 在上一篇 JS中的继承(上) 我们介绍了3种比较常用的js继承方法,如果你没看过,那么建议你先看一下,因为接下来要写的内容, 是建立在此基础上的.另外本文作为我个人的读书笔记,才疏 ...

  8. 详细理解JS中的继承

    正式说继承之前,有两个相关小点: JS只支持实现继承,即继承实际的方法,不支持接口继承(即继承方法的签名,但JS中函数没签名) 所有对象都继承了Object.prototype上的属性和方法. 说继承 ...

  9. js中的继承和重载

      js中有三种继承方式:一.通过原型(prototype)实现继承 二.借用构造函数式继承,可分为通过call()方法实现继承和通过apply()方法实现继承 仅仅通过原型继承我们可以发现在实例化子 ...

  10. JS中的继承方式总结

    1. 原型链继承(又称类继承) Child.prototype = new Parent(); function Parent (name, age) { this.name = name; this ...

随机推荐

  1. 关于ping命令的批处理问题

    需求描述:假设你的IP保存在名字为IP.txt的文本文档里,且每行一条ip.你想ping这些IP并得到结果 解决方案:用下面的批处理代码即可实现,将下面的代码保存为后缀为.bat的文件,比如test. ...

  2. JSP中request对象常用方法汇总

    setAttribute(String name,Object):设置名字为name的request的参数值 getAttribute(String name):返回由name指定的属性值 getAt ...

  3. css3应用

    画出一个禁行标志 border-radius: 50%; width: 100px; height: 100px; border: 10px solid red; background: linear ...

  4. bind() 方法

    一. 定义和用法 bind() 方法为被选元素添加一个或多个事件处理程序,并规定事件发生时运行的函数. 语法: $(selector).bind(event,data,function) 举例:  

  5. Computing Essentials_第一章习题

  6. MySQL5.7安装教程(RPM)

    博主本人平和谦逊,热爱学习,读者阅读过程中发现错误的地方,请帮忙指出,感激不尽 前言: 对应服务器信息: 192.168.247.53 一.MySQL安装(RPM) 1.系统环境设置: 1.1清空系统 ...

  7. spring顾问包装通知

    前边说到了顾问就是通知,没有实践,这里就实践一下,证明一下. 虽然可以说顾问就是通知,但是他们还是有的一定的区别的,通知是将目标类中所有的方法都进行的增强,而顾问却可以指定到特定的方法上,也就是说顾问 ...

  8. 从CVPR 2014看计算机视觉领域的最新热点

    2014看计算机视觉领域的最新热点" title="从CVPR 2014看计算机视觉领域的最新热点"> 编者按:2014年度计算机视觉方向的顶级会议CVPR上月落下 ...

  9. linux基本指令梳理

  10. win7+centos6.5安装双系统

    前言:之前在琢磨怎么安装双系统 倒腾了两天终于给装上了 使用软件 镜像:CentOS-6.5-x86_64-bin-DVD1.iso 开机引导软件 easybcd2.2 u盘制作软件 USBWrite ...