2018.5.27

今天本人又在查关于继承的问题,重新温习了一遍书,发现之前举的例子实际上不太清晰,故做调整。


我的上一篇文章介绍了,原型链继承模式。原型链继承虽然很强大,但是单纯的原型链模式并不能很好地实现继承。

一、原型链的缺点

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('快跑');
}

  所以,无论是单独使用原型链继承还是借用构造函数继承都有自己很大的缺点,最好的办法是,将两者结合一起使用,发挥各自的优势。我将在下一篇文章作出解释。js 继承之组合继承

如果你觉得文章解决了你的疑惑的话,还请赏我一个推荐哦~  :)

作者不才,文中若有错误,望请指正,避免误人子弟。

文章内容全都参考于《JAVASCRIPT 高级程序设计》)

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

  1. js继承之组合继承(结合原型链继承 和 借用构造函数继承)

    在我的前两篇文章中,我们已经介绍了 js 中实现继承的两种模式:原型链继承和借用构造函数继承.这两种模式都存在各自的缺点,所以,我们考虑是否能将这二者结合到一起,从而发挥二者之长.即在继承过程中,既可 ...

  2. javascript中继承(二)-----借用构造函数继承的个人理解

    本人目录如下: 零.寒暄&回顾 一,借用构造函数 二.事件代理 三,call和apply的用法 四.总结 零.寒暄&回顾 上次博客跟大家分享了自己对原型链继承的理解,想看的同学欢迎猛击 ...

  3. javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承

    javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...

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

    根据少一点套路,多一点真诚这个原则,继续学习. 借用构造函数继承 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术( ...

  5. JS高阶---继承模式(借用构造函数继承+组合继承)

    (1)借用构造函数继承 案例如下: 验证: (2)组合继承 案例如下: 验证如下: 结果如右图所示 . .

  6. javascript继承之借用构造函数与原型

    javascript继承之借用构造函数与原型 在js中,关于继承只有利用构造函数和原型链两种来现实.以前所见到的种种方法与模式,只不过是变种罢了. 借用构造函数 1 2 3 4 5 6 7 8 9 1 ...

  7. JavaScript各种继承方式(二):借用构造函数继承(constructor stealing)

    一 原理 在子类的构造函数中,通过call ( ) 或 apply ( ) 的形式,调用父类的构造函数来实现继承. function Fruit(name){ this.name = name; th ...

  8. javascript继承之借用构造函数(二)

    //简单的函数调用 function Father() { this.nums= [1,2]; } function Son() { Father.call(this);//调用超类型,完成son继承 ...

  9. js继承之原型链继承

    面向对象编程都会涉及到继承这个概念,JS中实现继承的方式主要是通过原型链的方法. 一.构造函数.原型与实例之间的关系 每创建一个函数,该函数就会自动带有一个 prototype 属性.该属性是个指针, ...

随机推荐

  1. java基础之类与对象2

    这是上一篇文章的源码................ public class Main 是我创建的一个主类 我的基本操作就在这个类里面完成,public static void main(Strin ...

  2. jmeter JDBC 连接数据库

    1.添加JDBC Connection Configuration 2.添加JDBC Request 3.添加查看结果树 4. 设置下列参数:Database URL:jdbc:mysql://hos ...

  3. 基于MVC和Bootstrap的权限框架解决方案 二.添加增删改查按钮

    上一期我们已经搭建了框架并且加入了列表的显示, 本期我们来加入增删改查按钮 整体效果如下 HTML部分,在HTML中找到中意的按钮按查看元素,复制HTML代码放入工程中 <a class=&qu ...

  4. jQuery animate()动画效果

    1.jQuery动画效果 jQuery animate()方法用于创建自定义动画 $(selector).animate({params},speed,callback); //必需的 params ...

  5. SQL Server 跨库复制表方法小笔记

    insert into tableA (column1,column2.....) SELECT * FROM OPENDATASOURCE('SQLOLEDB', 'Data Source=127. ...

  6. 什么是NoSQL

    NoSQL = Not Only SQL 不仅仅是SQL NoSQL,指的是非关系型的数据库(没有声明性查询语言,没有预定义的模式,可以为键 - 值对存储,列存储,文档存储,图形数据库).不同于传统的 ...

  7. Unity -JsonUtility的使用

    今天,为大家分享一下unity上的Json序列化,应该一说到这个词语,我们肯定会觉得,这应该是很常用的一个功能点:诚然,我们保存数据的时候,也许会用到json序列化,所以,我们有必要快速了解一下它的简 ...

  8. Linux-配置vim开发环境

    vim是一个类似于vi的著名的功能强大.高度可定制的文本编辑器,在vi的基础上改进和增加了很多特性.vim是纯粹的自由软件. 为了满足使用者的要求,将vim界面配置为自己想要的界面类型也变得流行起来. ...

  9. git的使用及常用命令

    一,GIT是什么? git是目前世界上最先进的分布式版本控制系统 Git是分布式版本控制系统,那么它就没有中央服务器的,每个人的电脑就是一个完整的版本库,这样,工作的时候就不需要联网了,因为版本都是在 ...

  10. 1. Skippr

    Skippr 是一个超级简单的 jQuery 幻灯片插件.只是包括你的网页中引入 jquery.skippr.css 和 jquery.skippr.js 文件就能使用了.Skippr 能够自适应窗口 ...