javascript中继承(二)-----借用构造函数继承的个人理解
本人目录如下:
零、寒暄&回顾
上次博客跟大家分享了自己对原型链继承的理解,想看的同学欢迎猛击这里,
上次说到原型链继承有一些问题,主要是两方面的。我们举个栗子来说明下:
Q1:共享的超类属性能被任何实例改写,这个是很危险的!看下面一段代码:
function Person(name){
this.name=name;
this.countries=["America","China","Canada"];
}
Person.prototype.sayName=function(){
return this.name;
}
function Student(age){
this.age=age;
}
Student.prototype=new Person();
var s1=new Student();
s1.countries.push("india");
console.log(s1.countries);//["America", "China", "Canada", "india"]
var s2=new Student();
console.log(s2.countries);//["America", "China", "Canada", "india"]
大家看到,我对s1对象的countries熟悉进行了修改,但是当我new出另外一个Studnet实例的时候,你会发现它的counties属性也被修改了。这段代码说明,从表面上看,任何对实例属性的修改,都会反映到其他实例上。
那么怎么解释这样的现象呢?用一张图来简要的说明下
大家可以看到,其实当把Person的实例赋给Student的原型后,countries属性其实已经存在了Student的原型对象中。而此时s1,s2实例均是指向Student的原型对象的,所以不论你怎么更改countries,都会反映到原型对象中的countries值中。这样就解释了为什么s2的countries属性也会被修改。
接下来,我们来看原型链继承的第二个问题:
Q2:在创建子类型的实例时,不能向超类型的构造函数中传递参数;看下面的一段代码:
function Person(name){
this.name=name;
this.countries=["America","China","Canada"];
}
Person.prototype.sayName=function(){
return this.name;
}
function Student(name,age){
this.age=age;
}
Student.prototype=new Person();
var s1=new Student("bob",25);
console.log(s1.name);//undefined
当我打印s1的name属性时,是undefined,这就是第二个问题的描述!好了,既然有问题,我们就来解决!
一,借用构造函数
function Person(name){
this.name=name;
this.countries=["America","China","Canada"];
}
function Student(name,age){
Person.call(this,name);//在这里借用了Person的构造函数
this.age=age;
}
var s1=new Student("bob",25);
s1.countries.push("india");
console.log(s1.countries);//["America", "China", "Canada", "india"]
console.log(s1.name);//bob
var s2=new Student("finnya",23);
console.log(s2.countries);//["America", "China", "Canada"]
console.log(s2.name);//finnya
从上面的代码中可以看到,通过是用call()方法(或者apply()方法),我们就是在Student实例的环境下调用了Person构造函数。这样一来,新的Student对象中就有了Person构造函数里面的初始化代码。结果就是Student的每个实例当中都有了countries属性的副本。
function Person(name){
this.city="北京";
this.name=name;
this.countries=["America","China","Canada"];
}
function Student(name,age){
this.city="上海";
Person.call(this,name);
this.age=age;
//this.city="上海";//如果将新增属性放在借用函数以后,就不会出现这个问题
}
var s1=new Student("bob",25);
s1.countries.push("india");
console.log(s1.city);//这里输出的是北京,前面新增的Student实例属性并没有体现出来
到此为止,关于借用构造函数的继承方法已经完成了。但是,细心的同学会发现,借用构造函数方法也有它自己弊端,因为每一个子类的对象实例都拥有自己的实例属性,这样一来函数复用就无从谈起了。而且在超类型的原型中定义的方法,对子类型而言也是不可见的。所以,其实我们一般在实现继承的过程中,都不会单纯的使用这种继承模式。那么这个问题怎么解决呢?请大家接着看下面一部分:
二,组合式继承
function Person(name){
this.name=name;
this.countries=["America","China","Canada"];
}
Person.prototype.sayName=function(){
return this.name;
}
function Student(name,age){
Person.call(this,name);
this.age=age;
}
Student.prototype=new Person();
Student.prototype.sayAge=function(){
return this.age;
}
var s1=new Student("bob",25);
s1.countries.push("india");
console.log(s1.countries);//["America", "China", "Canada", "india"]
console.log(s1.name);//bob
console.log(s1.sayAge())//
var s2=new Student("finnya",23);
console.log("------------------------");
console.log(s2.countries);//["America", "China", "Canada"]
console.log(s2.name);//finnya
console.log(s2.sayAge())//23
通过上面的例子,可以看到,当我们把两种继承的方式融合到一起以后,Student实例分别拥有了各自的name,countries属性,又共享了Person的原型方法sayName。所以说,组合继承避免了原型链和借用构造函数的缺点,融合了各自的优点。当然,这里我们仍然可以使用之前谈到的两种方法去检测实例和原型的关系,instanceof和isPrototypeOf(),具体怎么检测和其中的问题,请大家参考我之前一篇博客----原型链式继承。
好了,到这里,关于继承的事情基本谈完了,下面说一下一个知识扩充:
三,call和apply的用法
function foo(){
alert(1);
}
foo();
//同时,也可以这么调用
foo.call();
那么这里怎么解释这样的写法呢?前面我用红色的字体标记过一句话”函数只不过是在特定的环境中执行代码的对象“,我们使用call调用时,call函数的本质是需要执行这个函数的(这里可以联想到另外一个方法bind,它只是绑定函数并生成一个新的函数,并不去执行,大家一定要注意,有时间,我会将这个函数进行扩展!),那么其实这里相当于在window域里面去调用foo函数,所以这个问题就迎刃而解了,只是调用函数的一种方式而已。
({}).toString.apply(‘str’);//这里返回的是[object String]
上面的这种用法,一般是用来检测一个对象的类型,通常,
({}).toString.call([Class]);//返回的是[object Class],传入的参数不一样,出现的Class就不一样
说白了,我们前面的一段代码可以这样理解,我下面只是一个伪代码,可以这样去理解,但真正的结果还是有区别的!!!!
var str="str";
str.toStirng();
关于更多的理解,本文篇幅有限,有时间我会再去补充,大家有什么疑问,可以给我留言!
var arr1=["a","b","c"];
var arr2=["d","e","f"];
// arr1.push(arr2)
// console.log(arr1);//["a", "b", "c", Array[3]]
Array.prototype.push.apply(arr1,arr2);
console.log(arr1);//["a", "b", "c", "d", "e", "f"]
上面的第一种调用方法中只是把arr2整个数组加到了arr1中,但是第二种调用方式,能够出现正常的结果!什么原因呢?因为大家知道,apply方法的第二个参数是一个数组,这里在方法内部,肯定是是实现了一个数组循环,然后把数组中每个值分开进行push操作,这里正是利用了apply方法的这个特性,将arr2自动循环解析。
var arr1=["1","2","3"];
console.log(Math.max(arr1));//NaN
console.log(Math.max.apply(arr1)); //-Infinity
console.log(Math.max.apply(null,arr1));//
大家看上面的第一个调用,可以知道,其实Math.max()只能接受(1,,2,3)这样一个一个的值,传入数组时,它是没法工作的。这里我们想要Math.max()能够处理数组,那么只能请回apply来处理了,它能够将传入的数组,变成一个接一个的参数的形式,这样Math.max()就能够返回正常的最大值了。
四、总结
,给我了许多的参考。本人前端的菜鸟,文中肯定有理解不对的地方,希望各位前辈给我留言指导!时间仓促,有表述不对的地方,我会后续进行修改!望各位理解!PS:本文中的图片水印,是本人csdn的博客搬家到cnblogs所致,请大家不要误解本人抄袭!
http://www.cnblogs.com/rookiebob/,有些勘误我会及时更正,为了避免对您的误解,请访问原文!
javascript中继承(二)-----借用构造函数继承的个人理解的更多相关文章
- javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承
javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...
- js继承之借用构造函数继承
我的上一篇文章介绍了,原型链继承模式.但是单纯的原型链模式并不能很好地实现继承. 一.原型链的缺点 1.1 单纯的原型链继承最大的一个缺点,来自于原型中包含引用类型的值. 本来,我们没有通过原型链实现 ...
- js继承之组合继承(结合原型链继承 和 借用构造函数继承)
在我的前两篇文章中,我们已经介绍了 js 中实现继承的两种模式:原型链继承和借用构造函数继承.这两种模式都存在各自的缺点,所以,我们考虑是否能将这二者结合到一起,从而发挥二者之长.即在继承过程中,既可 ...
- JS继承之借用构造函数继承和组合继承
根据少一点套路,多一点真诚这个原则,继续学习. 借用构造函数继承 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术( ...
- javascript继承之借用构造函数与原型
javascript继承之借用构造函数与原型 在js中,关于继承只有利用构造函数和原型链两种来现实.以前所见到的种种方法与模式,只不过是变种罢了. 借用构造函数 1 2 3 4 5 6 7 8 9 1 ...
- JS高阶---继承模式(借用构造函数继承+组合继承)
(1)借用构造函数继承 案例如下: 验证: (2)组合继承 案例如下: 验证如下: 结果如右图所示 . .
- JavaScript各种继承方式(二):借用构造函数继承(constructor stealing)
一 原理 在子类的构造函数中,通过call ( ) 或 apply ( ) 的形式,调用父类的构造函数来实现继承. function Fruit(name){ this.name = name; th ...
- Javascript中,实现类与继承的方法和优缺点分析
Javascript是一种弱类型语言,不存在类的概念,但在js中可以模仿类似于JAVA中的类,实现类与继承 第一种方法:利用Javascript中的原型链 //首先定义一个父类 function An ...
- JavaScript中的原型链和继承
理解原型链 在 JavaScript 的世界中,函数是一等公民. 上面这句话在很多地方都看到过.用我自己的话来理解就是:函数既当爹又当妈."当爹"是因为我们用函数去处理各种&quo ...
随机推荐
- C++十进制转换为二进制
题目内容:将十进制整数转换成二进制数. 输入描述:输入数据中含有不多于50个的整数n(-231<n<231). 输出描述:对于每个n,以11位的宽度右对齐输入n值,然后输出“-->” ...
- Mayan游戏 (codevs 1136)题解
[问题描述] Mayan puzzle是最近流行起来的一个游戏.游戏界面是一个7行5列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上.游戏通关是指在规定的 ...
- [转]SQLServer 2008以上误操作数据库恢复方法——日志尾部备份
原文出处:http://blog.csdn.net/dba_huangzj/article/details/8491327 问题: 经常看到有人误删数据,或者误操作,特别是update和delete的 ...
- [terry笔记]Oracle会话追踪(一):SQL_TRACE&EVENT 10046
SQL_TRACE/10046 事件是 Oracle 提供的用于进行 SQL 跟踪的手段,在日常的数据库问题诊断和解决中是非常常用的方法.但其生成的trace文件需要tkprof工具生成一个可供人 ...
- Python学习教程(learning Python)--2 Python简单函数设计
本节讨论Python程序设计时为何引入函数? 为何大家都反对用一堆堆的单个函数语句完成一项程序的设计任务呢? 用一条条的语句去完成某项程序设计时,冗长.不宜理解,不宜复用,而采用按功能模块划分成函数, ...
- lnmp的使用
命令 1.状态管理 lnmp {start|stop|reload|restart|kill|status} 2.添加虚拟host lnmp vhost add
- DB2递归查询
斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1.1.2.3.5.8.13.21.……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n&g ...
- android获取手机录
在Android开发中,读取手机通讯录中的号码是一种基本操作,但是由于Android的版本众多,所以手机通讯录操作的代码比较纷杂,在本文中进行一下总结. Android1.5是现在的Android系统 ...
- iOS学习之Object-C语言集合
一.数组类 1.C语言数组的特点:数组是一个有序的集合,用来存储相同数据类型的元素,通过下标访问数组中的元素,下标从0开始. 2.OC中的数组只能存储对象类型(必须是NSObjec ...
- iOS学习之C语言循环结构
一.while循环 while (循环条件) { 循环体: } // 1.定义循环变量 int time = 1; // 2.循环条件 while ( ...