原型链继承
function Person() {
this.score = 100;
this.teacher = ["wang"];
}
Person.prototype.age = 18;
function Student(name) {
this.name = name;
}
Student.prototype = new Person();
var stu1 = new Student("lily");
var stu2 = new Student("aa");
//---输出age-----------------------------------
console.log(stu2.age);//18
console.log(stu1.age);//18
//---更改age,然后输出--------------------------
Person.prototype.age = 10;
console.log(stu1.age);//10
console.log(stu2.age);//10
//---更改父类的teacher--------------------------
stu1.teacher.push("zhang");
console.log(stu1.teacher);// ['wang','zhang']
console.log(stu2.teacher);// ['wang','zhang']
//---实例对象继承了Person和Student--------------
console.log(stu1 instanceof Person);//true
console.log(stu1 instanceof Student);// true
- 把父类的实例对象赋给子类构造函数的prototype
- 特点:实例对象可以继承父类Person构造方法中的属性、父类原型对象上的属性。
- 缺点:
-
- 无法向父类的构造函数传参
- 继承单一
-
- 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
构造函数继承
盗用构造函数,这种方法相当于通过call、apply方法将父类的构造方法中的属性在实例对象上复制了一遍,这样保证了每个实例对象都有唯一的的父类构造方法中的属性(hobby,score),当一个实例对象中的hobby、score发生改变,其他实例对象不收影响的。
function Person(hobby) {
this.score = 100;
this.hobby = hobby;
}
function Student(name, hobby) {
Person.call(this, hobby);
this.name = name;
}
var stu1 = new Student("lily", "sing");
var stu2 = new Student("aa", "dance");
console.log(stu1.hobby);// sing
console.log(stu2.hobby);// dance
console.log(stu1.age); // undefined
console.log(stu2.age); // undefined
console.log(stu1.score);//100
console.log(stu1 instanceof Person);// false
console.log(stu1 instanceof Student);// true
- 用call 改变Person 的 this 指向,此时this指向的当前的实例对象。只是改变指向的关系(类似于把Person中的属性赋制给了实例对象),此时实例并不会继承Person.prototype中的属性。
- Person.prototype 并没在 实例对象的原型链上。
- 特点:
-
- 实例对象只继承Person构造方法中的属性。
- 解决了原型链无法向父类构造方法传参、所用实例共享父类属性
-
- 可以使用call() 让实例对象继承多个构造函数中的属性(不继承这些构造方法prototype中的属性)。
- 在子类中可以向父类传参
- 缺点:
-
- 无法继承父类的prototype中的属性
- 无法实现原型链上属性的复用。
-
- 每个新实例都有父类构造函数的副本,臃肿。
组合继承(最经典的)
这种是构造方法继承和原型链继承结合。
思路:原型链上继承原型上的属性和方法。通过盗用构造函数让实例继承父类构造函数中的属性。
这样,既可以实现原型链上的属性复用,也可已让每个实例中的父类构造函数中的属性互相不污染。
function Person(hobby) {
this.score = 100;
this.hobby = hobby;
this.teacher = ["wang"];
}
Person.prototype.age = 18;
function Student(name, hobby) {
Person.call(this, hobby);
this.name = name;
}
Student.prototype = new Person();
var stu1 = new Student("lily", "sing");
console.log(stu1.hobby);// sing
console.log(stu1.age); // 18
stu1.teacher.push("zhang"); // ['wang']
console.log(stu1.teacher); // ['wang','zhang']
console.log(stu1 instanceof Person); // true
- 结合了这两种方法的优点,并且这两种方法的缺点互补。
- 特点:
-
- 可以继承父类原型上的属性,可以传参,可复用
- 每个新实例引入的构造函数属性是私有的。
- 缺点:调用了两次父类构造函数(耗内存)
四、原型式继承
通过一个函数容器,里面创建一个构造函数,让这个构造函数继承Person实例。用容器封起来,这样所有实例中的属性互不影响。
function Person(hobby) {
this.score = 100;
this.hobby = hobby;
this.teacher = ["wang"];
}
Person.prototype.age = 18;
function Content(obj) {
function fn1() {
this.name = "lily";
}
fn1.prototype = obj;
return new fn1();
}
const father = new Person("game");
const stu1 = Content(father);
const stu2 = Content(father);
console.log(stu1);
console.log(stu1.hobby);//game
console.log(stu1.age);// 18
stu2.hobby = "sing";
console.log(stu2.hobby);//sing
console.log(stu1.hobby);//game
console.log(stu1 instanceof Person);//true
- 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
- 类似于复制一个对象,用函数来包装。
- 缺点:
-
- 所有实例都会继承原型上的属性
- 无法实现复用。(新实例属性都是后面添加的)
五、寄生式继承
function Content(obj) {
function fn1() {}
fn1.prototype = obj;
return new fn1();
}
function createAnotherObj(originObj) {
const obj = Content(originObj);
obj.name = name;
return obj;
}
var father = new Person("game");
var stu1 = createAnotherObj(father, "lily");
console.log(stu1);
console.log(stu1 instanceof Person);// true
- 重点:就是给原型式继承外面套了个壳子。
- 优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
- 缺点:没用到原型,无法复用。无法更改父类的属性
寄生组合式
组合继承其实存在效率问题,最主要的是:父类构造函数始终会被调用两次,第一次:创建子类原型是调用,另一次是在子类构造函数中调用。本质上子类原型中要包含超类(父类)对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。