答案:
-
原型继承
-
构造继承
-
组合继承(组合原型+构造)
-
实例继承
-
寄生组合继承
-
ES6 使用class extends继承
详细解析:
首先定义 父类Animal
//父类
function Animal(name){
this.name = name;
this.sum = function() {
alert(this.name);
}
}
Animal.prototype.age = 10; //父类原型属性
一、原型链继承:子类的原型等于父类的实例
instanceof 判断对象的具体类型(判断元素是否在另一个元素的原型链上),这里 dog1 继承了 父类Animal 的属性,返回true。
实例是子类的实例,也是父类的实例。
//原型链实现继承
function Dog() {
this.name = "xu";
}
Dog.prototype = new Animal(); //关键语句,子类型的原型指向父类型的实例
// 这里实例化一个 Animal 时, 实际上执行了两步
// 1,新创建的对象复制了父类构造函数内的所有属性及方法
// 2,并将原型 __proto__ 指向了父类的原型对象
var dog1 = new Dog();
console.log(dog1.age); //10
console.log(dog1 instanceof Animal); //true
console.log(dog1 instanceof Dog); //true
结果:
特点:
- 实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(父类新增原型方法/原型属性,子类都能访问的到)
缺点:
- 新实例无法向父类构造函数传参。
- 继承单一,无法实现多继承。
- 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
二、构造函数继承:用 .call() 在子类的构造函数内部调用父类构造函数
实例并不是父类的实例,只是子类的实例,这里具体为 cat1 不是 父类Animal 的实例,因此返回false。
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类的构造函数中向父类构造函数传递参数。
//构造函数实现继承
function Cat() {
//调用 Animal 构造函数
Animal.call(this, "animalType"); //关键语句,使用call()方法继承父类构造函数中的属性
//为了保证父类的构造函数不会重写子类的属性,需要在调用父类构造函数后,定义子类的属性
this.age = "5"; //子类属性
}
//子类实例
var cat1 = new Cat();
console.log(cat1.name); //animalType
console.log(cat1.age); //5
console.log(cat1 instanceof Animal); //false
console.log(cat1 instanceof Cat); //true
结果:
特点:
- 只继承了父类构造函数的属性,没有继承父类原型的属性。
- 解决了 原型继承 缺点1、2、3。不会造成原型属性共享的问题。
- 可以继承多个构造函数属性(call多个)。
- 在子实例中可向父实例传参。
缺点:
- 只能继承父类构造函数的属性/方法,不能继承原型属性/方法。
- 无法实现构造函数的复用。(每次用每次都要重新调用)
- 实例并不是父类的实例,只是子类的实例。
- 每个新实例都有父类构造函数的副本,影响性能,臃肿。
三、组合继承:结合了(原型链+构造)两种模式的优点,传参和复用
主要思想:使用原型继承使用对原型属性和方法的继承,通过构造函数继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
//组合继承
function Cat(name) {
Animal.call(this, name); //构造继承 ----第二次调用 父类Animal----
}
// ----第一次调用 父类Animal----
Cat.prototype = new Animal(); //原型继承
Cat.prototype.constructor = Cat; //组合继承需要修复构造函数的指向
var cat = new Cat("miao");
console.log(cat.name); //miao 继承了构造函数属性
console.log(cat.age); //10 继承了父类原型的属性
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
结果:
特点:
- 可以继承实例属性/方法,也可以继承原型属性/方法。不存在引用属性共享问题,可传参,可复用。
- 每个新实例引入的构造函数属性是私有的。
缺点:
调用了两次父类构造函数(耗内存),生成了两份实例。子类的构造函数会代替原型上的那个父类构造函数。
四、实例继承:为父类实例添加新特性,作为子类实例返回
实例是父类的实例,不是子类的实例,这里具体为 cat1 不是 子类Cat 的实例,因此返回false
//实例继承核心:为父类实例添加新特性,作为子类实例返回
function Cat(name) {
var instance = new Animal();
instance.name = 'cc';
return instance;
}
var cat = new Cat();
console.log(cat.name); //cc
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //false
结果:
特点:
- 不限制调用方式,不管是new子类()还是子类(),返回的对象都具有相同的效果
缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
五、寄生组合继承
核心:在组合继承中,调用了两次父类构造函数,这里 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。
主要思想:借用 构造函数 继承 属性 ,通过 原型链的混成形式 来继承 方法。
// 父类
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
return this.name;
};
// 子类
function SubType (name, subName) {
// 调用 SuperType 构造函数
SuperType.call(this, name); // ----第二次调用 SuperType,继承实例属性----
this.subName = subName;
};
// ----第一次调用 SuperType,继承原型属性----
SubType.prototype = Object.create(SuperType.prototype)
SubType.prototype.constructor = SubType; // 注意:增强对象
let instance = new SubType('An', 'sisterAn')
六、ES6 使用 class extends 继承
//ES6 class extends 继承
class Person{
constructor(name, age){ //constructor构造函数
this.name=name;
this.age=age;
}
show(){
console.log(this.name);
console.log(this.age);
}
}
class Worker extends Person{ //子类继承父类
constructor(name, age, job){
super(name, age);
this.job=job;
}
showJob(){
console.log(this.job);
}
}
let worker1 = new Worker('jia', 21, '班长');
worker1.show();
worker1.showJob();
结果:
通过class创建对象看起来就比较简洁,不用通过原型去实现继承。
在class的继承中,子类的_proto_属性表示构造函数的继承,指向父类,子类prototype属性的_proto_属性,表示方法的继承,总是指向父类的prototype属性。
class调用必须通过new 关键字。
class中的static 属性:用于定义类中的静态方法和静态属性关键字。
该方式声明的方法与属性,只能通过类名调用,可被继承,并且定义方法的 this 指向类 而不是实例。
ES中的function 可以用call apply bind 的方式 来改变他的执行上下文,但是 class 不可以 。