前言
昨天简单讲了原型的理解,今天我继续来讲一下原型的继承,以前面试遇到这种都是直接放弃的类型,希望今天过后,再遇到同样的问题,多多少少可以说出一点东西,那么今天这篇文章就算没有白写。
原型的继承方法
JavaScript中原型的继承方法有原型继承、利用构造函数继承、组合继承、组合寄生式继承
原型继承
原型继承就是直接更改对象的prototype
属性以达到继承的效果。
function Person() {
this.name = 'lili'
}
Person.prototype.sayName = function () {
console.log(this.name)
}
function Teacher(age) {
this.age = age
}
Teacher.prototype = new Person()
Teacher.prototype.constructor = Teacher
const t1 = new Teacher(18)
console.log(t1.name, t1.age) // lili 18
t1.sayName() // lili
console.log(Teacher.prototype.constructor === Teacher) // true
从打印结果我们可以知道,Teacher
中并没有定义name
属性及sayName
方法,但是我们仍然可以在Teacher
的实例中使用,这就是继承,可以理解为子类可以从父类中继承属性和方法,需要注意的是,我们使用了Teacher.prototype.constructor = Teacher
修改了构造函数的指向,如果不加这一行,最后一行的打印结果会是false
。
利用构造函数实现继承
利用构造函数实现继承可以理解成在字类中直接调用父类,并指定执行父类的this
指向。
function Person() {
this.name = 'lili'
}
Person.prototype.sayName = function () {
console.log(this.name)
}
function Teacher(age) {
Person.apply(this) // 可以理解为这儿直接执行了一个函数,函数体只有一句话,this.name = 'lili',this指向和Teacher中相同
this.age = age
}
const t1 = new Teacher(18)
console.log(t1.name, t1.age) // lili 18
// t1.sayName() // t1.sayName is not a function
console.log(Teacher.prototype.constructor === Teacher)
可以看到t1.sayName()
直接报错,说明这种继承方法只能继承父类中显示定义的属性和方法,不能继承父类原型上面的方法。
组合继承
组合继承就是直接将原型集成和构造函数继承结和起来,我们直接修改上面的例子。
function Person() {
this.name = 'lili'
}
Person.prototype.sayName = function () {
console.log(this.name)
}
function Teacher(age) {
Person.apply(this) // 可以理解为这儿直接执行了一个函数,函数体只有一句话,this.name = 'lili',this指向和Teacher中相同
this.age = age
}
Teacher.prototype = new Person()
Teacher.prototype.constructor = Teacher // 修正构造函数
const t1 = new Teacher(18)
console.log(t1.name, t1.age) // lili 18
t1.sayName() // lili
console.log(Teacher.prototype.constructor === Teacher) // true
可以看出,这种继承方式可以同时继承父类显示定义的属性和方法及父类原型上的属性和方法。但是这种继承方法也有问题,就是构造函数Person
执行了两次,一次是在Teacher.prototype = new Person()
时,还有一次是在new Teacher()
时,Teacher
中使用Person.apply(this)
调用了一次。这样字类实例和字类的原型中都有name
属性,造成了资源浪费。
组合寄生式继承
组合寄生式继承式在组合继承的基础上改造而来,不直接调用Teacher.prototype = new Person()
修改原型指向,而是利用一个中间对象来链接字类和父类。
function Person() {
this.name = 'lili'
}
Person.prototype.sayName = function () {
console.log(this.name)
}
function Teacher(age) {
Person.apply(this) // 可以理解为这儿直接执行了一个函数,函数体只有一句话,this.name = 'lili',this指向和Teacher中相同
this.age = age
}
function extend(childObj,parentObj) {
const tempObj = Object.create(parentObj.prototype)
tempObj.constructor = childObj
childObj.prototype = tempObj
}
extend(Teacher,Person)
const t1 = new Teacher(18)
console.log(t1.name, t1.age) // lili 18
t1.sayName() // lili
console.log(Teacher.prototype.constructor === Teacher) // true
这样修改后,构造函数Person
执行一次,属性name
也只在子类的实例中有,子类原型上没有这个属性,代码变得更加纯粹。