原型链继承
首先我们得知道什么是原型链。原型就是一个构造函数有prototype这个属性,这个属性指向的是一个对象,而这个对象就是构造函数的原型;通过new 构造函数生成的实例中有__proto__属性,这个__proto__属性指向原型对象。那原型链是啥呢?首先我们想一个问题:函数是一个对象,对象有原型,原型也是对象,那原型是不是也有对应的原型呢。开始套娃,就形成了原型链。原型链的顶端是啥呢,所有的对象都是通过Object函数new出来的,那顶端就是Object对象,Object的__proto__就指向null。
言归正传,说一下原型链继承
function Person() {
this.eating = function () {
console.log('eating')
}
this.name = 'wkb'
}
function Student(age) {
this.age = age
}
Student.prototype = new Person()
Student.prototype.studying = function () {
console.log('studying')
}
let stu = new Student(20)
stu.studying()
Student子类的构造函数的原型指向Person父类的实例对象,这样就继承了父类Person的原型上的数据。画个图吧
这样很明显有很多缺点:
- 只会继承父类原型上的属性,并不会继承父类上的属性
- 修改父类原型上的属性时,都会受到影响
借用构造函数继承
借用call来改变this指向,继承父类的属性。
function Person(n, m) {
this.eating = function () {
console.log('eating')
}
this.name = n
this.class = m
}
function Student(age, n, m) {
this.age = age
Person.call(this, n, m)
}
Student.prototype = new Person()
Student.prototype.studying = function () {
console.log('studying')
}
let stu = new Student(20, 'wkb', '一班')
console.log(stu)
我们可以看到在Student构造函数中调用Person的构造函数,并通过call来改变Person函数this指向,然后赋值,这样就可以在原型链继承的基础上,继承父类Person的属性。
但是,这样依然有很多缺点:
- 调用了两次Person构造函数,造成性能的浪费。
- 会继承了父类不必要的属性
原型式继承
首先我们要达到继承父类的原型上的属性,必须要新建一个对象,该对象指向父类的原型,子类的原型指向该对象。这样才不会生成多余的属性。我们有三种方法来实现:
- 原型函数
function createPrototype(obj) {
let fn = function () {}
fn.prototype = obj
let newObj = new fn()
return newObj
}
- Object.setPrototypeOf
function createPrototype2(obj) {
let newObj = {}
Object.setPrototypeOf(newObj, obj)
return newObj
}
- Object.create
let info = Object.create(Person)
这三种都是实现了创建一个空对象,空对象的原型指向父类,然后返回这个空对象
寄生式继承
通过Object.create来继承父类原型上的熟悉
let person = {
eating: function () {
console.log('eating')
},
}
function createStudent(name) {
// 内部创建了一个对象,对象的原型是传入的对象,然后返回创建的对象
var stu = Object.create(person)
stu.name = name
stu.studying = function () {
console.log('studying')
}
return stu
}
let stuObj = createStudent('wkb')
console.log(stuObj)
stuObj.eating()
这种方法很可以继承父类原型上的属性,但是只适用于Object对象,不适用于构造函数
寄生组合式继承
相对比上面提到的,这种方法是最好继承方法。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eating = function () {
console.log('eating')
}
Person.prototype.runing = function () {
console.log('runing')
}
function Student(name, age, className, studentId) {
Person.call(this, name, age)
this.className = className
this.studentId = studentId
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student // 不重新指向话,打印出来的stu是Person类型
let stu1 = new Student('wkb', 20, 'compute', '20181251150')
stu1.eating()
通过call继承Person父类内部的属性,再通过Object.create继承Person父类原型上的属性。注意一点:在修改Student原型指向后,我们打印Student的实例stu1,则发现是Person类型,这是因为,Student的原型被修改,则对应的constructor的指向也被修改。当前Studnet的原型是一个空对象,空对象的原型是Person父类的原型,Person父类的原型中有Constructor属性,指向Person构造函数,则打印出来的则是Person类型。 如下图
我们发现这种只适用一种继承,我们可以封装一个函数,来达到复用的目的。如下
function inherit(subObj, superObj) {
let Fn = function () {}
Fn.prototype = superObj.prototype
subObj.prototype = new Fn()
Object.defineProperty(subObj.prototype, 'constructor', {
enumerable: false,
writable: true,
configurable: true,
value: subObj,
})
}