第一种:原型链继承
利用原型链的特点进行继承
function Parent(){
this.name = 'web前端';
this.type = ['JS','HTML','CSS'];
}
Parent.prototype.Say = function(){
console.log(this.name);
}
function Son(){};
Son.prototype = new Parent();
son1 = new Son();
son1.Say();
创建一个父 Parent的构造函数,里面有两个属性name、type。通过Parent构造函数的属性设置Say方法,此时,Parent有两个属性和一个方法。然后创建Son子构造函数。设置Son属性值为父构造函数Parent的实例对象,即子构造函数Son继承了父构造函数Parent,此时Son也有2个属性和1个方法。对Son构造函数进行实例化,结果赋值给变量son1,即son1为实例化对象,同样拥有2个属性和一个方法。
优点:可以实现继承。
缺点:①因为Son.prototype(即原型对象)继承了Parent实例化对象,这就导致了所有Son实例化对都一样,都共享有原型对象的属性及方法。② Son构造函数实例化对象无法进行参数的传递。
第二种:构造函数继承
通过构造函数call 方法进行继承,或者apply()调用父类型构造函数
function Parent(){
this.name = '前端';
this.type = ['JS','HTML','CSS'];
}
function Son(){
Parent.call(this);
}
son1 = new Son();
son1.type.push('VUE');
console.log(son1.type);//['JS','HTML','CSS','VUE']
son2 = new Son();
console.log(son2.type);//['JS','HTML','CSS']
首先创建父级构造函数Parent,有name、type两个属性。创建子级构造函数Son,函数内部通过call 方法调用父级构造函数Parent,实现继承。分别创建构造函数Son的两个实例化对象son1,son2,对son1 的 type属性新增元素,son2没有新增。
优点: ① 实现实例化对象的的独立性;② 还可以给实例化对象添加参数。
缺点: ① 方法都在构造函数中定义,每次实例化对象都创建一遍方法,基本无法实现函数复用。②call方法仅仅调用了父级构造函数的属性及方法,没有办法调用父级构造函数原型对象的方法。
第三种:组合继承:原型链+借用构造函数(最常用)
function Parent(name){
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function(){
console.log(this.name);
}
function Child(name, age){
// 核心代码 1
Parent.call(this, name);
this.age = age;
}
// 核心代码 2 子类型的原型为父类型的一个实例对象
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 可以通过子类给父类的构造函数传参
let child1 = new Child('litterStar');
let child2 = new Child('luckyStar');
child1.getName(); // litterStar
child2.getName(); // luckyStar
child1.colors.push('pink');
// 修改 child1.colors 不会影响 child2.colors
console.log(child1.colors); // [ 'red', 'blue', 'yellow', 'pink' ]
console.log(child2.colors); // [ 'red', 'blue', 'yellow' ]
注意核心代码: Parent.call(this, name);和 Child.prototype = new Parent();
融合了原型链继承和借用构造函数的饿优点,称为JavaScript种最常用的继承模式。
缺点:调用了两次父类构造函数,生成了两份实例
- 一次是设置子类型实例的原型的时候 Child.prototype = new Parent();
- 一次是创建子类型实例的时候 let child1 = new Child(‘litterStar’);, 调用 new 会执行 Parent.call(this, name);,此时会再次调用一次 Parent构造函数。
第四种:原型式继承 (Object.create)
借助原型可以基于现有方法来创建对象,var B = Object.create(A) 以A对象为原型,生成A对象,B继承了A的所有属性和方法。
const person = {
name: 'star',
colors: ['red', 'blue'],
}
// 核心代码:Object.create
const person1 = Object.create(person);
const person2= Object.create(person);
person1.name = 'litterstar';
person2.name = 'luckystar';
person1.colors.push('yellow');
console.log(person1.colors); // [ 'red', 'blue', 'yellow' ]
console.log(person2.colors); // [ 'red', 'blue', 'yellow' ]
特点: 没有严格意义上的构造函数,借助原型可以基于已有对象创建新对象
缺点:来自原型对象的所有属性被所有实例共享,person1修改 colors 会影响person2的 colors,这点跟原型链继承一样。
第五种:寄生式继承
创建一个用于封装继承过程的函数,该函数在内部以某种方式来增强对象
function createObj (original) {
// 通过调用函数创新一个新对象
var clone = Object.create(original);
// 以某种方式来增强这个对象
clone.sayName = function () {
console.log('hi');
}
// 返回这个对象
return clone;
}
缺点:每次创建对象都会创建一遍方法,跟借助构造函数模式一样。
第六种:寄生组合式继承(最理想的)
Object.create MDN上的解释:它会创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function() {
console.log(this.name)
}
function Child(name, age) {
// 核心代码①
Parent.call(this, name);
this.age = age;
}
// 核心代码②
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
核心代码: Parent.call(this, name);和 Child.prototype = Object.create(Parent.prototype);
寄生组合式继承,集寄生式继承和组合式继承的优点,是引用类型最理想的继承范式。
第七种:ES6 中class的继承
ES6中引入了class关键字,可以通过extends关键字实现继承。
class Parent {}
class Child extends Parent {
constructor(name, age, color) {
// 调用父类的constructor(name, age)
super(name, age);
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。