继承,同样不是真正严格意义上面向对象的继承,而是通过javascript中的原型链关系实现函数之间的属性,方法共享.下面简单分享几种封装的方法.
既然说到继承,我们必须有一个基类
1 2 3 4 5 6 7 8 9 |
function Person(){ this.eat=function(){ return '吃食物'; } }
Person.prototype.sport=function(){ return '运动'; } |
Person 这个基类包含了2个属性,吃和运动.但是奇怪的是在构造函数(当然是模拟的)中定义了一个属性 eat ,我们赋值了一个方法,方法返回吃食物.
然后又在Person 类的原型上定义了一个属性 sport ,赋值一个方法,返回运动.
其实这2个属性完全可以都放在 构造函数,或者全部定义在原型对象上,没有问题,我们这里分别定义只是为了演示后面子类继承基类后实例化的对象能不能100%继承基类里定义的这2种方式呢? 我们下面一一举例查看.
(一)第一种继承方式,构造函数里绑定
下面我要定义一个子类,学生,让学生继承人,并且在学生的构造函数里绑定基类 Person
1 2 3 4 |
function Student(name){ Person.apply(this,arguments); this.name=name; } |
如何实现的绑定,就是javascript中的 apply 和 call 方法,这2个方法实现目的相同,只是参数格式不一样,看这里 call 和 apply 实现继承的区别
我用JS Runner 来演示一下,为了在工具下方打印出来,重写了 print 函数:
1 2 3 4 5 6 7 |
var print=function(text){ var t=''; for(item in arguments){ t+= arguments[item]+','; } document.write(t+'<br/>'); } |
你可以不关注上面这个函数,如果你在chrome 的控制台调试, mac下 option + command +i 即可掉出来,用 console.log 就可以输出结果.
红框是工具方法,为了在下发打印出来,重写了 print 方法.
蓝框是基类 Person
绿框是子类 Student
黑框是输出语句, name 属性和 study() 方法是子类定义的.打印没有问题, eat() 方法是基类定义,看了继承了基类这个方法,但是基类还有一个原型上的方法 sport() ,我们再来看下这个方法. xiaoming.sport() ,结果报错,如下:
1 |
TypeError: undefined is not a function (evaluating 'xiaoming.sport()') |
到这里你就看到了构造函数绑定的弊端了,它是有局限的,在子类 构造函数里绑定基类,只能继承来自基类构造函数定义的属性,方法,对于基类原型 prototype 对象上定义的属性,方法都是不能被子类继承的.
(二)子类原型继承基类
还是刚才的例子,Student 的原型就是 Student.prototype
1 |
Student.prototype=new Person(); |
这一句的含义就是把子类原型指针指向基类的实例.这样也可以继承基类的属性方法.
和上面的图不同的有2个地方,红框部分是子类继承基类,打印的时候最后把基类的2个方法都打印出来了.
看上去相当完美,基类构造函数的属性和基类原型链上的属性统统能继承.
(三) 子类原型继承基类原型
和上面的方法大同小异,区别就是把上面基类的对象变成了基类的原型
上面图片和第一幅图片有2处差异,红框处 Student.prototype 继承了 基类 Person.prototype
绿框处输出了子类的所有属性,方法,但是,基类的方法只有原型上的可以输出,我们尝试输出 xiaoming.eat(); 结果报错:
1 |
32 TypeError: undefined is not a function (evaluating 'xiaoming.eat()') |
子类原型继承基类原型过程中,基类构造函数定义的属性,方法都将不会继承到子类.