在ES5继承的实现非常有趣的,由于没有传统面向对象类的概念,Javascript利用原型链的特性来实现继承,这其中有很多的属性指向和需要注意的地方。
原型链的特点和实现已经在之前的一篇整理说过了,就是通过将子类构造函数的原型作为父类构造函数的实例(sub.prototype=new super),这样就连通了子类-子类原型-父类,原型链的特点就是逐层查找,从子类开始一直往上直到所有对象的原型Object.prototype,找到属性方法之后就会停止查找,所以下层的属性方法会覆盖上层。
一个基本的基于原型链的继承过程大概是这样的:
- //先来个父类,带些属性
- function Super(){
- this.flag = true;
- }
- //为了提高复用性,方法绑定在父类原型属性上
- Super.prototype.getFlag = function(){
- return this.flag;
- }
- //来个子类
- function Sub(){
- this.subFlag = false;
- }
- //实现继承
- Sub.prototype = new Super;
- //给子类添加子类特有的方法,注意顺序要在继承之后
- Sub.prototype.getSubFlag = function(){
- return this.subFlag;
- }
- //构造实例
- var es5 = new Sub;
原型链实现的继承主要有几个问题:
1、本来我们为了构造函数属性的封装私有性,方法的复用性,提倡将属性声明在构造函数内,而将方法绑定在原型对象上,但是现在子类的原型是父类的一个实例,自然父类的属性就变成子类原型的属性了;这就会带来一个问题,我们知道构造函数的原型属性在所有构造的实例中是共享的,所以原型中属性的改变会反应到所有的实例上,这就违背了我们想要属性私有化的初衷;
2、创建子类的实例时,不能向父类的构造函数传递参数
- function Super(){
- this.flag = true;
- }
- function Sub(){
- this.subFlag = false;
- }
- Sub.prototype = new Super;
- var obj = new Sub();
- obj.flag = flase; //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响
- var obj_2 = new Sub();
- console.log(obj.flag) //false;
为了解决以上两个问题,有一个叫借用构造函数的方法
只需要在子类构造函数内部使用apply或者call来调用父类的函数即可在实现属性继承的同时,又能传递参数,又能让实例不互相影响
- function Super(){
- this.flag = true;
- }
- function Sub(){
- Super.call(this) //如果父类可以需要接收参数,这里也可以直接传递
- }
- var obj = new Sub();
- obj.flag = flase;
- var obj_2 = new Sub();
- console.log(obj.flag) //依然是true,不会相互影响
结合借用构造函数和原型链的方法,可以实现比较完美的继承方法,可以称为 组合继承 :
- function Super(){
- this.flag = true;
- }
- Super.prototype.getFlag = function(){
- return this.flag; //继承方法
- }
- function Sub(){
- this.subFlag = flase
- Super.call(this) //继承属性
- }
- Sub.prototype = new Super;
- var obj = new Sub();
- Sub.prototype.constructor = Sub;
- Super.prototype.getSubFlag = function(){
- return this.flag;
- }
- function Box(age) {
- this.name = ['Lee', 'Jack', 'Hello']
- this.age = age;
- }
- Box.prototype.run = function () {
- return this.name + this.age;
- };
- function Desk(age) {
- Box.call(this, age); //对象冒充
- }
- Desk.prototype = new Box(); //原型链继承
- var desk = new Desk(100);
- alert(desk.run());
这里还有个小问题,Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super;
然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:
Sub.prototype.constructor = Sub;
看完ES5的实现,再来看看ES6的继承实现方法,其内部其实也是ES5组合继承的方式,通过call借用构造函数,在A类构造函数中调用相关属性,再用原型链的连接实现方法的继承
- class B extends A {
- constructor() {
- return A.call(this); //继承属性
- }
- }
- A.prototype = new B; //继承方法
ES6封装了class,extends关键字来实现继承,内部的实现原理其实依然是基于上面所讲的原型链,不过进过一层封装后,Javascript的继承得以更加简洁优雅地实现
- class ColorPoint extends Point {
- constructor(x, y, color) {
- super(x, y); // 等同于parent.constructor(x, y)
- this.color = color;
- }
- toString() {
- return this.color + ' ' + super.toString(); // 等同于parent.toString()
- }
- }
通过constructor来定义构造函数,用super调用父类的属性方法
转至:http://www.jianshu.com/p/342966fdf816