js继承之借用构造函数继承
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继承之借用构造函数继承的更多相关文章
- js继承之组合继承(结合原型链继承 和 借用构造函数继承)
在我的前两篇文章中,我们已经介绍了 js 中实现继承的两种模式:原型链继承和借用构造函数继承.这两种模式都存在各自的缺点,所以,我们考虑是否能将这二者结合到一起,从而发挥二者之长.即在继承过程中,既可 ...
- javascript中继承(二)-----借用构造函数继承的个人理解
本人目录如下: 零.寒暄&回顾 一,借用构造函数 二.事件代理 三,call和apply的用法 四.总结 零.寒暄&回顾 上次博客跟大家分享了自己对原型链继承的理解,想看的同学欢迎猛击 ...
- javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承
javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...
- JS继承之借用构造函数继承和组合继承
根据少一点套路,多一点真诚这个原则,继续学习. 借用构造函数继承 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术( ...
- JS高阶---继承模式(借用构造函数继承+组合继承)
(1)借用构造函数继承 案例如下: 验证: (2)组合继承 案例如下: 验证如下: 结果如右图所示 . .
- javascript继承之借用构造函数与原型
javascript继承之借用构造函数与原型 在js中,关于继承只有利用构造函数和原型链两种来现实.以前所见到的种种方法与模式,只不过是变种罢了. 借用构造函数 1 2 3 4 5 6 7 8 9 1 ...
- JavaScript各种继承方式(二):借用构造函数继承(constructor stealing)
一 原理 在子类的构造函数中,通过call ( ) 或 apply ( ) 的形式,调用父类的构造函数来实现继承. function Fruit(name){ this.name = name; th ...
- javascript继承之借用构造函数(二)
//简单的函数调用 function Father() { this.nums= [1,2]; } function Son() { Father.call(this);//调用超类型,完成son继承 ...
- js继承之原型链继承
面向对象编程都会涉及到继承这个概念,JS中实现继承的方式主要是通过原型链的方法. 一.构造函数.原型与实例之间的关系 每创建一个函数,该函数就会自动带有一个 prototype 属性.该属性是个指针, ...
随机推荐
- activiti 5.15.1 动态手动通过java编码方式,实现创建用户任务,动态指定个人,用户组,角色,指定监听的实现
因为我们的业务需要,最近一直在搞动态动过java程序实现为用户任务绑定监听程序.碰了很多壁,查看了API文档,最后终于在找到解决办法,所以贴出来,希望能够留个底,也能帮助有需要的人. -------- ...
- 一些CSS/JS小技巧
CSS部分 1.文本框不可点击 .inputDisabled{ background-color: #eee;cursor: not-allowed;} 2.禁止复制粘贴 onpaste=" ...
- javascript数组常用方法详解
1,splice(). array.splice(index,many,list1,list2....) 参数1.index位置 负数为从结尾处算,倒数第一为-1:参数2,many要删除的项目, ...
- EF的DbSet属性的Where查询,注意事项
#1 Func<T,bool>与 Expression<Func<T,bool>>的区别 Func<T,bool>本身就是一个委托(delegate), ...
- html5表单元素详解
表单是Html中获取用户输入的手段.此文对表单的元素进行了详细整理. 表单基本元素 form input button form元素 html4中,form元素相当于表单的外包装,其他都要在里面.ht ...
- PHP环境搭建之PHPstorm9+PHP5开发环境配置
以前写过一篇zend studio+WAMP的:点这里,个人感觉写得不怎么好可是阅读数却上千了... 不过笔者身边好多人开始用PHPStrom了,所以就简单的写个教程 一.下载安装 PHPStrom下 ...
- GTK简单了解记录
GTK+http://zh.wikipedia.org/wiki/GTK%2B#.E5.9B.BE.E5.BD.A2.E6.97.A0.E5.85.B3.E4.BB.A3.E7.A0.81 GTK+最 ...
- test back
python Mysql 下载地址 http://sourceforge.net/projects/mysql-python/
- Docker学习总结(一)
<认识Docker> 不定期更新~~~~~~~ 历史区别: 13年之前:网络大多使用"协议栈堆叠"的形式进行开发,需要部署单一专有的服务器进行操作.包括(中间件,运行时 ...
- SDN学习之OpenFlow协议分析
学习SDN相关的学习也已经有快半年了,期间从一无所知到懵懵懂懂,再到现在的有所熟悉,经历了许多,也走了不少弯路,其中,最为忌讳的便是,我在学习过程中,尚未搞明白OpenFlow协议的情况下,便开始对S ...