一 、原型链继承
function Person() {
this.name = 'Person'
}
Person.prototype.eating = function () {
console.log(this.name + 'eating~')
}
// 子类:特有属性和方法
function Student() {
this.sno = 111
}
const p = new Person()
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student()
console.log(stu.name) // Person
stu.eating() // Person eating~
stu.studying() // Person studying~
特点: 1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新 实例不会继承父类实例的属性!
缺点: 1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属 性,另一个实例的原 型属性也会被修改!)
二、借用构造函数继承
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function () {
console.log(this.name + ' eating~')
}
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = 111
}
const p = new Person()
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student('Student', 18, ['kobe'], 111)
console.log(stu.name) // Student
stu.eating() // Student studying~
stu.studying()//Student eating~
重点: 用 .call()和.apply() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复 制))
特点: 1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点 1、2、3。
3、可以继承多个构造函数属性(call 多个)。
4、在子实例中可向父实例传参。
缺点: 1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
三、组合继承(组合原型链继承和借用构造函数继承)(常用)
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function () {
console.log(this.name + ' eating~')
}
//子类
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = 111
}
// 直接将父类的原型赋值给子类, 作为子类的原型
// 直接将 Person.prototype 赋值给 Student.prototype 会导致 Student.prototype 和 Person.prototype 指向同一个对象,因此对 Student.prototype 的修改也会影响到 Person.prototype,因此使用`Object.create` 来创建一个新的对象,并将其赋值给 `Student.prototype`,以保持继承关系的正确性
Student.prototype = Object.create(Person.prototype)
Student.prototype.studying = function () {
console.log(this.name + ' studying~')
}
const stu = new Student('student', 18, ['friend'], 111)
console.log(stu) // Person { name: 'student', age: 18, friends: [ 'friend' ], sno: 111 }
stu.eating() // student eating~
重点: 结合了两种模式的优点,传参和复用
特点: 1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点: 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函 数。
四、原型式继承
let obj = {
name: 'why',
age: 18
}
let info = Object.create(obj)
console.log(info)
console.log(info.__proto__)
重点: 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的 实例或对象。object.create()就是这个原理。
特点: 类似于复制一个对象,用函数来包装。
缺点: 1、所有实例都会继承原型上的属性。 2、无法实现复用。(新实例属性都是后面添加的)
五、寄生式继承
let personObj = {
running: function () {
console.log('running')
}
}
function createStudent(name) {
let stu = Object.create(personObj)
stu.name = name
stu.studying = function () {
console.log('studying~')
}
return stu
}
let stuObj = createStudent('why')
let stuObj1 = createStudent('kobe')
let stuObj2 = createStudent('james')
重点: 就是给原型式继承外面套了个壳子。
优点: 没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成 了创建的新对象。
缺点: 没用到原型,无法复用。
六、寄生组合式继承
function Parent() {
this.name = "parent";
this.fun = () => {
console.log("fun");
};
this.friends = [1, 2, 3];
}
Parent.prototype.me = () => {
console.log("11");
};
function Child() {
Parent.call(this);
this.sex = 1;
}
function clone(parent, child) {
// 这里改用Object.create可以减少组合继承中执行父类构造函数的次数
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
// Child.prototype = new Parent();
// Child.prototype.constructor = Child;//封装这两句话
clone(Parent, Child);
let child1 = new Child();
let child2 = new Child();
console.log(
child1.name === child2.name,
child1.fun === child2.fun,
child1.friends === child2.friends
);//true false false
child1.name = "xiaogui";
child1.fun.a = 1;
child1.friends.push(4);
console.log(child1.name, child2.name);//xiaogui parent
console.log(child1.fun.a, child2.fun.a);// 1 undefined
console.log(child1.friends, child2.friends);//[1,2,3,4] [1,2,3]
child2.me();//11
这个方法比较成熟 前面继承方式弊端基本解决 只调用一次父类构造函数 Child可以向Parent传参 父类方法可以复用 父类的引用属性不会被共享 ES6中的类继承extends也是使用寄生组合继承原理