ES5和ES6中对于继承的实现方法

在ES5继承的实现非常有趣的,由于没有传统面向对象类的概念,Javascript利用原型链的特性来实现继承,这其中有很多的属性指向和需要注意的地方。

原型链的特点和实现已经在之前的一篇整理说过了,就是通过将子类构造函数的原型作为父类构造函数的实例(sub.prototype=new super),这样就连通了子类-子类原型-父类,原型链的特点就是逐层查找,从子类开始一直往上直到所有对象的原型Object.prototype,找到属性方法之后就会停止查找,所以下层的属性方法会覆盖上层。

一个基本的基于原型链的继承过程大概是这样的:

[javascript]  view plain  copy
  1. //先来个父类,带些属性  
  2. function Super(){  
  3.     this.flag = true;  
  4. }  
  5. //为了提高复用性,方法绑定在父类原型属性上  
  6. Super.prototype.getFlag = function(){  
  7.     return this.flag;  
  8. }  
  9. //来个子类  
  10. function Sub(){  
  11.     this.subFlag = false;  
  12. }  
  13. //实现继承  
  14. Sub.prototype = new Super;  
  15. //给子类添加子类特有的方法,注意顺序要在继承之后  
  16. Sub.prototype.getSubFlag = function(){  
  17.     return this.subFlag;  
  18. }  
  19. //构造实例  
  20. var es5 = new Sub;  

原型链实现的继承主要有几个问题:

1、本来我们为了构造函数属性的封装私有性,方法的复用性,提倡将属性声明在构造函数内,而将方法绑定在原型对象上,但是现在子类的原型是父类的一个实例,自然父类的属性就变成子类原型的属性了;这就会带来一个问题,我们知道构造函数的原型属性在所有构造的实例中是共享的,所以原型中属性的改变会反应到所有的实例上,这就违背了我们想要属性私有化的初衷;
2、创建子类的实例时,不能向父类的构造函数传递参数

[javascript]  view plain  copy
  1. function Super(){  
  2.     this.flag = true;  
  3. }  
  4. function Sub(){  
  5.    this.subFlag = false;  
  6. }  
  7. Sub.prototype = new Super;  
  8. var obj = new Sub();  
  9. obj.flag = flase;  //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响  
  10. var obj_2 = new Sub();  
  11. console.log(obj.flag)  //false;  

为了解决以上两个问题,有一个叫借用构造函数的方法
只需要在子类构造函数内部使用apply或者call来调用父类的函数即可在实现属性继承的同时,又能传递参数,又能让实例不互相影响

[javascript]  view plain  copy
  1. function Super(){  
  2.     this.flag = true;  
  3. }  
  4. function Sub(){  
  5.     Super.call(this)  //如果父类可以需要接收参数,这里也可以直接传递  
  6. }  
  7. var obj = new Sub();  
  8. obj.flag = flase;  
  9. var obj_2 = new Sub();  
  10. console.log(obj.flag)  //依然是true,不会相互影响  

结合借用构造函数和原型链的方法,可以实现比较完美的继承方法,可以称为 组合继承

[javascript]  view plain  copy
  1. function Super(){  
  2.     this.flag = true;  
  3. }  
  4. Super.prototype.getFlag = function(){  
  5.     return this.flag;     //继承方法  
  6. }  
  7. function Sub(){  
  8.     this.subFlag = flase  
  9.     Super.call(this)    //继承属性  
  10. }  
  11. Sub.prototype = new Super;  
  12. var obj = new Sub();  
  13. Sub.prototype.constructor = Sub;  
  14. Super.prototype.getSubFlag = function(){  
  15.     return this.flag;  
  16. }  
[javascript]  view plain  copy
  1. function Box(age) {  
  2. this.name = ['Lee''Jack''Hello']  
  3. this.age = age;  
  4. }  
  5. Box.prototype.run = function () {  
  6. return this.name + this.age;  
  7. };  
  8. function Desk(age) {  
  9. Box.call(this, age); //对象冒充  
  10. }  
  11. Desk.prototype = new Box(); //原型链继承  
  12. var desk = new Desk(100);  
  13. alert(desk.run());  


这里还有个小问题,Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super;
然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:
Sub.prototype.constructor = Sub;

看完ES5的实现,再来看看ES6的继承实现方法,其内部其实也是ES5组合继承的方式,通过call借用构造函数,在A类构造函数中调用相关属性,再用原型链的连接实现方法的继承

[javascript]  view plain  copy
  1. class B extends A {  
  2.   constructor() {  
  3.     return A.call(this);  //继承属性  
  4.   }  
  5. }  
  6. A.prototype = new B;  //继承方法  

ES6封装了class,extends关键字来实现继承,内部的实现原理其实依然是基于上面所讲的原型链,不过进过一层封装后,Javascript的继承得以更加简洁优雅地实现

[javascript]  view plain  copy
  1. class ColorPoint extends Point {  
  2.   constructor(x, y, color) {  
  3.     super(x, y); // 等同于parent.constructor(x, y)  
  4.     this.color = color;  
  5.   }  
  6.   toString() {  
  7.     return this.color + ' ' + super.toString(); // 等同于parent.toString()  
  8.   }  
  9. }  

通过constructor来定义构造函数,用super调用父类的属性方法

转至:http://www.jianshu.com/p/342966fdf816

猜你喜欢

转载自blog.csdn.net/wdr2003/article/details/80662872
今日推荐