上次博客跟大家分享了自己对原型链继承的理解,想看的同学欢迎猛击 这里, 上次说到原型链继承有一些问题,主要是两方面的。我们举个栗子来说明下:
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,这就是第二个问题的描述!好了,既然有问题,我们就来解决!
一,借用构造函数
在解决问题的过程中,借用构造函数也可以成为:“伪造对象”,“经典继承”。这种技术也就说在子类型构造函数的内部调用超类型构造函数,其实函数只不过是在特定环境中执行代码的对象(这句话很经典,在《高程》上面看到的,初学js的同学可以反复揣摩这句话,相信你每一遍对这句话的思考,都能有新的启发,说不定你之前有些不能理解的问题,这里能下子豁然开朗!) 好了,有了上面这些铺垫,我们只需要一种工具,能够在子类型的构造函数中调用超类型的构造函数。这里apply()和call()就派上用场了。 说到这里,之前也看了很多关于call和apply的介绍,这个函数看起来很神秘,其实说起来就一句话,在特定的函数环境里执行另外一个函数! 看下面一段代码:- 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
- 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())//25
- 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
三,call和apply的用法
之前,到网上查询这两个方法的使用,看了很多的文章,给大家列举下一些不太常见(或者说特殊)的用法:第一种,大家知道,调用一个方法时,我们可以使用:
- function foo(){
- alert(1);
- }
- foo();
- //同时,也可以这么调用
- foo.call();
第二种用法,
- ({}).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"]
第四种用法,我们来让Math.max能够从数组中取出最大的元素
- 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));//3
【致谢】依旧感谢《高程》的陪伴,给我了许多的参考。本人前端的菜鸟,文中肯定有理解不对的地方,希望各位前辈给我留言指导!时间仓促,有表述不对的地方,我会后续进行修改!望各位理解!