js继承之借用构造函数继承

一、原型链的缺点

1.1 单纯的原型链继承最大的一个缺点,在于对原型中引用类型值的误修改。

  先看一个例子:

//父类:人
    function Person () {
      this.head = ‘脑袋瓜子‘;
    }
    //子类:学生,继承了“人”这个类
    function Student(studentID) {
      this.studentID = studentID;
    }
    Student.prototype = new Person();

    var stu1 = new Student(1001);
    console.log(stu1.head); //脑袋瓜子

    stu1.head = ‘聪明的脑袋瓜子‘;
    console.log(stu1.head); //聪明的脑袋瓜子

    var stu2 = new Student(1002);
    console.log(stu2.head); //脑袋瓜子

以上例子,我们通过重写 Student.prototype 的值为 Person 类的一个实例,实现了 Student 类对 Person 类的继承。所以 ,stu1 能访问到父类 Person 上定义的 head 属性,打印值为“脑袋瓜子”。我们知道,所有的 Student 实例都共享着原型对象上的属性。那么,如果我在 stu1 上改变了 head 属性值,是不是会影响原型对象上的 head 值呢?看我上面的代码就知道,肯定是不会。stu1 的 head 值确实是改变了,但是我重新实例化的对象 stu2 的 head 值仍旧不变。

  这是因为,当实例中存在和原型对象上同名的属性时,会自动屏蔽原型对象上的同名属性stu1.head = "聪明的脑袋瓜子" 实际上只是给 stu1 添加了一个本地属性 head 并设置了相关值。所以当我们打印 stu1.head 时,访问的是该实例的本地属性,而不是其原型对象上的 head 属性(它因和本地属性名同名已经被屏蔽了)。

  刚才我们讨论的这个 head 属性是一个基本类型的值,可如果它是一个引用类型呢?这其中又会有一堆小九九。

  其实原型对象上任何类型的值,都不会被实例所重写/覆盖。在实例上设置与原型对象上同名属性的值,只会在实例上创建一个同名的本地属性。

  但是,原型对象上引用类型的值可以通过实例进行修改,致使所有实例共享着的该引用类型的值也会随之改变。

  再看下面这个例子:

//父类:人
    function Person () {
      this.head = ‘脑袋瓜子‘;
      this.emotion = [‘喜‘, ‘怒‘, ‘哀‘, ‘乐‘]; //人都有喜怒哀乐
    }
    //子类:学生,继承了“人”这个类
    function Student(studentID) {
      this.studentID = studentID;
    }
    Student.prototype = new Person();

    var stu1 = new Student(1001);
    console.log(stu1.emotion); //[‘喜‘, ‘怒‘, ‘哀‘, ‘乐‘]

    stu1.emotion.push(‘愁‘);
    console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]

    var stu2 = new Student(1002);
    console.log(stu2.emotion); //["喜", "怒", "哀", "乐", "愁"]

我们在刚才的 Person 类中又添加了一个 emotion 情绪属性,人都有喜怒哀乐嘛。尤其需要注意的是,这是一个引用类型的值。这时,stu1 认为他还很“愁”,所以就通过 stu1.emotion.push ( ) 方法在原来的基础上增加了一项情绪,嗯,打印出来“喜怒哀乐愁”,没毛病。可是 stu2 是个乐天派,他咋也跟着一起愁了呢?!肯定不对嘛~

  这就是单纯的原型链继承的缺点,如果一个实例不小心修改了原型对象上引用类型的值,会导致其它实例也跟着受影响。

  因此,我们得出结论,原型上任何类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改。

1.2 原型链不能实现子类向父类中传参。这里就不细说了。

二、借用构造函数

2.1 实现原理

  在解决原型对象中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数的技术。实现原理是,在子类的构造函数中,通过 apply ( ) 或 call ( )的形式,调用父类构造函数,以实现继承。

//父类:人
    function Person () {
      this.head = ‘脑袋瓜子‘;
      this.emotion = [‘喜‘, ‘怒‘, ‘哀‘, ‘乐‘]; //人都有喜怒哀乐
    }
    //子类:学生,继承了“人”这个类
    function Student(studentID) {
      this.studentID = studentID;
      Person.call(this);
    }

    //Student.prototype = new Person();

    var stu1 = new Student(1001);
    console.log(stu1.emotion); //[‘喜‘, ‘怒‘, ‘哀‘, ‘乐‘]

    stu1.emotion.push(‘愁‘);
    console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]

    var stu2 = new Student(1002);
    console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

细心的同学可能已经发现了,该例子与上面的例子非常相似,只是去掉了之前通过 prototype 继承的方法,而采用了 Person.call (this) 的形式实现继承。别忘了,函数只不过是一段可以在特定作用域执行代码的特殊对象,我们可以通过 call 方法指定函数的作用域。

  (题外话:也许有的同学对 this 的指向还不完全清楚,我是这么理解的:谁调用它,它就指向谁。)

  在 stu1 = new Student ( ) 构造函数时,是 stu1 调用 Student 方法,所以其内部 this 的值指向的是 stu1, 所以 Person.call ( this ) 就相当于Person.call ( stu1 ),就相当于 stu1.Person( )。最后,stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了 stu1 上。stu2 也是同理,所以其实是,每个实例都具有自己的 emotion 属性副本。他们互不影响。说到这里,大家应该清楚一点点了吧。

  总之,在子类函数中,通过call ( ) 方法调用父类函数后,子类实例 stu1, 可以访问到 Student 构造函数和 Person 构造函数里的所有属性和方法。这样就实现了子类向父类的继承,而且还解决了原型对象上对引用类型值的误修改操作。

2.2 缺点

  这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,比如 eat()。这样做,有几个缺点:

  1. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。(函数复用又无从谈起了,本来我们用 prototype 就是解决复用问题的)

  2. 方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。

//父类:人
    function Person () {
      this.head = ‘脑袋瓜子‘;
      this.emotion = [‘喜‘, ‘怒‘, ‘哀‘, ‘乐‘]; //人都有喜怒哀乐
      this.eat = function () {
        console.log(‘吃吃喝喝‘);
      }
      this.sleep = function () {
        console.log(‘睡觉‘);
      }
      this.run = function () {
        console.log(‘快跑‘);
      }
    }

所以,无论是单独使用原型链继承还是借用构造函数继承都有自己很大的缺点,最好的办法是,将两者结合一起使用,发挥各自的优势

引用http://www.cnblogs.com/sarahwang/p/6879161.html

原文地址:https://www.cnblogs.com/ymh2013/p/9392386.html

时间: 2024-11-08 22:55:41

js继承之借用构造函数继承的相关文章

JS继承之借用构造函数继承和组合继承

根据少一点套路,多一点真诚这个原则,继续学习. 借用构造函数继承 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承).这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数. 基本模式 function SuperType(){ this.colors = ["red", "blue", "green"]; } fu

javascript继承,原型继承,借用构造函数继承,混合继承

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getS

JavaScript继承-借用构造函数继承

借用构造函数继承是在子类型构造函数的内部调用超类型狗在函数,通过使用apply()和call()方法 function girlFriend(){ this.girls = ['chen','wang','zhu']; } function Person(){ girlFriend.call(this,20); } var wang = new Person(); var zhu = new Person(); wang.girls.push('zhang'); console.log(wang

js组合继承(原型继承+借用构造函数继承)

组合继承就是将原型链和构造函数结合起来发挥二者优势的一种模式.继承:是类型与类型之间的继承继承目的:把子类型相同成员提取到父类型,实现代码重用 大体思路是:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,很好地解决了原型链继承的缺点,是较为常用的一种继承方式.//一:借用构造函数(借用父类型extend) 缺点:无法继承父类型的方法//父类型 function Person(name,age,sex){ this.anme=name;this.age=age;th

JavaScript中的继承之借用构造函数

借用构造函数是为了解决引用值类型被所有实例共享的问题. 基本思想是:在子类型构造函数内部通过apply()或call()方法调用超类型的构造函数,也可以在将来新创建的对象上执行构造函数. 先看一个例子: function superType(){ this.colors = ['red','blue','green']; } function subType(){ //继承了superType superType.call(this); } var instance1 = new subType

js面向对象编程(二) 构造函数继承

构造函数绑定 //基类建筑物var building = function () {    this.spec = "building";}; //address:房子地址,toward:房子朝向var house = function (address, toward) {    this.address = address;    this.toward = toward;}; //使房子继承建筑物 //使用call或者apply方法,将父对象的构造函数绑定在子对象上,在子对象构造

js继承之二(组合模式=借用构造函数+原型链方式)

借用构造函数模式:不能继承原型上的属性,可以避免引用类型修改问题 原型链:能够继承原型上的属性,会发生引用类型修改 so:敲黑板! function CarModel(c){ this.color=c||"白色"; this.arr=[1,2,3]; this.getColor=function(){ console.log('我的颜色是'+this.color); } } CarModel.prototype.test="lla"; function Car(br

282 继承模式:原型链继承 : 得到方法,借用构造函数 : 得到属性,组合,new一个对象背后做了些什么

1.原型链继承 : 得到方法 function Parent(){} Parent.prototype.test = function(){}; function Child(){} Child.prototype = new Parent(); // 子类型的原型指向父类型实例 Child.prototype.constructor = Child var child = new Child(); //有test() <!DOCTYPE html> <html lang="e

JavaScript之原型式继承&amp;寄生式继承和寄生组合式继承以及优缺点

一.原型式继承 1.这种方法并没有使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象 function object(o) { function F() {} F.prototype = o; return new F(); } // 在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数原型,最后返回了这个临时类型的一个新实例. // object()本质上对其中传入的对象进行了一次浅复制 // 看如下的例子: var person = { na