这篇文章主要介绍JavaScript实现继承的方式:javascript
一、类式继承java
简单的类式继承:数组
// 声明父类function Animal() { this.name = 'animal'; this.type = ['pig', 'cat'];}
// 为父类添加共有方法Animal.prototype.greet = function(sound) { console.log(sound);}
// 声明子类function Dog() { this.name = 'dog';}
// 继承父类Dog.prototype = new Animal();
var dog = new Dog();dog.greet('汪汪'); // "汪汪"console.log(dog.type); // ["pig", "cat"]复制代码
在上面的代码中,咱们建立了两个类Animal和Dog,并且给Animal.prototype
原型上添加了一个greet共有方法,而后经过new
命令实例化一个Animal,而且赋值给Dog.prototype
原型。bash
原理说明:在实例化一个类时,新建立的对象复制了父类的构造函数内的属性与方法而且将原型__proto__
指向了父类的原型对象,这样就拥有了父类的原型对象上的属性与方法。app
不过,经过类式继承方式,有两个缺点。函数
第一个是引用缺陷:ui
dog.type.push('dog');var dog2 = new Dog();console.log(dog2.type); // ["dog", "cat", "dog"]复制代码
经过上面的执行结果,咱们看到当经过dog实例对象修改继承自Animal中的数组type(引用类型)时,另一个新建立的实例dog2也会受到影响。this
第二个是咱们没法为不一样的实例初始化继承来的属性,咱们能够修改一下上面的例子:spa
function Animal(color) { this.color = color;}...Dog.prototype = new Animal('白色');...console.log(dog.color); // "白色"console.log(do2.color); // "白色"复制代码
经过上面的代码能够看到,咱们没法为不一样dog赋值不一样的颜色,全部dog只能同一种颜色。prototype
二、构造函数继承
构造函数继承方式能够避免类式继承的缺陷:
// 声明父类function Animal(color) { this.name = 'animal'; this.type = ['pig','cat']; this.color = color;}
// 添加共有方法Animal.prototype.greet = function(sound) { console.log(sound);}
// 声明子类function Dog(color) { Animal.apply(this, arguments);}
var dog = new Dog('白色');var dog2 = new Dog('黑色');
dog.type.push('dog');console.log(dog.color); // "白色"console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]console.log(dog2.color); // "黑色"复制代码
首先要知道apply
方法的运用,它是能够更改函数的做用域,因此在上面的例子中,咱们在Dog子类中调用这个方法也就是将Dog子类的变量在父类中执行一遍,这样子类就拥有了父类中的共有属性和方法。
可是,构造函数继承也是有缺陷的,那就是咱们没法获取到父类的共有方法,也就是经过原型prototype
绑定的方法:
dog.greet(); // Uncaught TypeError: dog.greet is not a function复制代码
三、组合继承
组合继承其实就是将类式继承和构造函数继承组合在一块儿:
// 声明父类 function Animal(color) { this.name = 'animal'; this.type = ['pig','cat']; this.color = color; }
// 添加共有方法 Animal.prototype.greet = function(sound) { console.log(sound); }
// 声明子类 function Dog(color) { // 构造函数继承 Animal.apply(this, arguments); } // 类式继承Dog.prototype = new Animal();
var dog = new Dog('白色'); var dog2 = new Dog('黑色');
dog.type.push('dog'); console.log(dog.color); // "白色"console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]console.log(dog2.color); // "黑色"dog.greet('汪汪'); // "汪汪"
复制代码
在上面的例子中,咱们在子类构造函数中执行父类构造函数,在子类原型上实例化父类,这就是组合继承了,能够看到它综合了类式继承和构造函数继承的优势,同时去除了缺陷。
可能你会奇怪为何组合式继承能够去除类式继承中的引用缺陷?其实这是因为原型链
来决定的,因为JavaScript引擎在访问对象的属性时,会先在对象自己中查找,若是没有找到,才会去原型链中查找,若是找到,则返回值,若是整个原型链中都没有找到这个属性,则返回undefined。
也就是说,咱们访问到的引用类型(好比上面的type)实际上是经过apply
复制到子类中的,因此不会发生共享。
这种组合继承也是有点小缺陷的,那就是它调用了两次父类的构造函数。
五、寄生组合式继承
寄生组合式继承强化的部分就是在组合继承的基础上减小一次多余的调用父类的构造函数:
function Animal(color) { this.color = color; this.name = 'animal'; this.type = ['pig', 'cat'];}
Animal.prototype.greet = function(sound) { console.log(sound);}
function Dog(color) { Animal.apply(this, arguments); this.name = 'dog';}
/* 注意下面两行 */Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() { console.log(this.name);}
var dog = new Dog('白色'); var dog2 = new Dog('黑色');
dog.type.push('dog'); console.log(dog.color); // "白色"console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]console.log(dog2.color); // "黑色"dog.greet('汪汪'); // "汪汪"复制代码
在上面的例子中,咱们并不像构造函数继承同样直接将父类Animal的一个实例赋值给Dog.prototype,而是使用Object.create()
进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype,这样子类上就能拥有了父类的共有方法,并且少了一次调用父类的构造函数。
Object.create()的浅拷贝的做用类式下面的函数:
function create(obj) { function F() {}; F.prototype = obj; return new F();}复制代码
这里还需注意一点,因为对Animal的原型进行了拷贝后赋给Dog.prototype,所以Dog.prototype上的constructor
属性也被重写了,因此咱们要修复这一个问题:
Dog.prototype.constructor = Dog;复制代码
六、extends继承
Class和extends
是在ES6中新增的,Class
用来建立一个类,extends
用来实现继承:
class Animal { constructor(color) { this.color = color; } greet(sound) { console.log(sound); } }
class Dog extends Animal { constructor(color) { super(color); this.color = color; } }
let dog = new Dog('黑色'); dog.greet('汪汪'); // "汪汪"console.log(dog.color); // "黑色"
复制代码
在上面的代码中,建立了父类Animal,而后Dog子类继承父类,两个类中都有一个constructor构造方法,实质就是构造函数Animal和Dog。
不知道你有没有注意到一点,我在子类的构造方法中调用了super方法,它表示父类的构造函数,用来新建父类的this对象。
注意:
子类必须在constructor
方法中先调用super
方法才可使用this ,不然新建实例时会报错。这是由于子类没有本身的this对象,而是继承父类的this对象,而后对其进行加工。若是不调用super方法,子类就得不到this对象。
ES5的继承,实质是先创造子类的实例对象this,而后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制彻底不一样,实质是先创造父类的实例对象this(因此必须先调用super方法),而后再用子类的构造函数修改this。
在子类的构造函数中,只有调用super以后,才可使用this关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。