一、原型继承
原型继承是Javascript中最基本也是最核心的继承方式,如何实现原型链的继承呢?由于Javascript中所有的对象都拥有原型属性(即__proto__属性),该属性指向父对象,实现对象之间的继承可以通过指定某个对象(即子对象)的原型__proto__为其父对象。在实践中我们可以使用如下方式来实现原型继承:
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
this.name = name;
}
// 指定Child的原型为Parent
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow, red
在上述代码中,我们声明了一个Parent函数和一个Child函数,Parent 函数中有 name 属性和 sayHello 方法,Child 函数中只有 name 属性,我们实现了 Child 的原型继承自 Parent,并把 Child的原型的构造器设置为 Child自己,实例化 Child 后就可以访问到 Parent的属性和方法。
实践中请注意:
- 实现原型继承时,需要在既定的基础上对其进行开发,建议使用封装性较好的函数进行开发,方便以后在其他项目也可以复用。
- 你也可以通过Object.create函数进行原型继承,这个函数可以上述代码整合成如下:
function object(p) {
function F() {}
F.prototype = p;
return new F();
}
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
this.name = name;
}
// 指定Child的原型为Parent
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow, red
二、经典继承
经典继承,即利用 call 或者 apply 方法来实现继承。经典继承相对于原型继承来说,显得更加灵活、高效。在设计模式中,很多模式基于经典继承的思想来构建,这些设计模式中都在使用 call 和 apply 方法来实现调用父类构造函数。在实践中我们可以使用如下方式来实现经典继承:
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
// 将 Parent 的作用域赋给 Child,以实现父类实例化
Parent.call(this, name);
this.age = 18;
}
var child = new Child('child');
alert(child.name); // child
alert(child.age); // 18
alert(child.colors); // undefined
我们声明了两个函数,Parent 函数中有 name 属性和 sayHello 方法,Child 函数中通过使用 Parent.call() 将 Parent 函数的作用域赋给了 Child,从而实现了 Child 对象继承 Parent 对象的属性和方法。这种方式不会污染父类的原型属性,同时也可以在子类实例化时传入属性,实际开发中应用广泛。
实践中请注意:
- 经典继承可以实现单继承或者多重继承,如下例中既同时继承自Father和Mother:
function Father() {
this.name = 'father';
}
function Mother() {
this.age = 30;
}
function Child() {
Father.call(this);
Mother.call(this);
}
var child = new Child();
console.log(child.name); // father
console.log(child.age); // 30
三、组合继承
组合继承,即将原型继承和经典继承进行组合来实现继承。通过使用原型链实现对超类属性(即方法)的共享,在通过调用父类构造函数来实现 prototype 中属性的定义。在实践中我们可以使用如下方式来实现组合继承:
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow
我们声明了两个函数,Parent 函数中有 name 属性和 sayHello 方法,Child 函数中在调用 Parent.call(this, name) 的情况下实现经典继承,然后在原型链上继承了 Parent 的属性和方法,最后将 Child 的构造函数重新赋值给 Child 的原型构造函数,从而实现 Child 对象的属性和方法的继承。关于组合继承,在实践中是一种非常好的方式。
实践中请注意:
- 在编写组合继承时,需要注意不要重复实例化父对象,我们可以使用 Object.create(Parent.prototype) 的方式来创建一个中介者。
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow
四、寄生组合继承
寄生组合继承,即相对于组合继承来说不会重复实例化父对象,而是创建一个空的函数作为中介者来实现继承。在实践中我们可以使用如下方式来实现寄生组合继承:
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
Parent.call(this, name);
}
var __ = function() {};
__.prototype = Parent.prototype;
Child.prototype = new __();
Child.prototype.constructor = Child;
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow
在上述代码中,我们通过创建名( __ )叫占位符(thunk)的函数作为中介者来实现继承,并将 Child 的原型指向这个中介器,避免了在使用 Object.create() 方法的重复实例化问题。实践证明在大多数情况下,寄生组合继承是一种更好的继承方式,我们还可以通过Module模式对寄生组合继承进行封装,易于复用。
实践中请注意:
- 当某个父类中有多个子类时,我们可以将父类中公共的方法提取出来,在Child构造函数的里面进行调用,达到代码复用的目的。
function Parent(name){
this.name = name;
this.sayHello = function(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
function Child(name){
Parent.call(this, name);
}
function extend(child, parent){
var Middle = function(){};
Middle.prototype = parent.prototype;
child.prototype = new Middle();
child.prototype.constructor = child;
child.prototype.parent = parent.prototype;
return child;
}
extend(Child, Parent);
var child1 = new Child('child1');
var child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow
五、ES6 继承
ES6 提出了 extends 关键字,可以极大的简化继承的操作,我们只需要在子类中添加 extends 关键字,并在解析 super 关键字时使用 new 关键字调用父类的构造函数即可。在实践中我们可以使用如下方式来实现ES6继承:
class Parent{
constructor(name){
this.name = name;
}
sayHello(){
alert('hello ' + this.name);
}
}
Parent.prototype.colors = ['blue', 'black', 'yellow'];
class Child extends Parent{
constructor(name){
super(name)
}
}
let child1 = new Child('child1');
let child2 = new Child('child2');
child1.sayHello(); // hello child1
child2.sayHello(); // hello child2
alert(child1.colors); // blue, black, yellow
child1.colors.push('red');
alert(child2.colors); // blue, black, yellow
在上述代码中,我们声明了一个 Parent 类和一个 Child 类,Child 类直接继承自 Parent 类,我们只需要在 Child 类中添加 extends 关键字,并在解析 super 关键字时使用 new 关键字调用父类的构造函数即可,如果是需要继承父对象的属性和方法,则直接使用 super 关键字即可。ES6 继承更加直观舒适,使用起来也非常方便。
实践中请注意:
- ES6继承在编码时需要注意浏览器的兼容性问题,如果是在较低版本的浏览器中使用的话,需要通过 babel 来实现ES6的编译。
- 可以在ES6中通过 Symbol.hasInstance 重