什么是继承及继承的好处:
面向对象中的三大特性:封装,继承,多态,继承就是继承父构造函数的属性和方法。这样的话就可以实现代码的复用,且继承是多态的前提。
开篇说重点:
// 父类:公共的属性和方法
function Person() {
this.name = 'lhq'
}
Person.prototype.eating = function() {
console.log(this.name + 'eating')
}
// 子类: 特有的属性和方法
function Student() {
this.sno = '111'
}
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}
const stu = new Student()
// 这个显然是取不到值的,因为Student构造函数中并没有定义name属性,那他的实例对象中必然是没有该属性的
// 那我们如果想要继承Person构造函数中的name属性该如何做呢?
console.log(stu.name)
stu.studying()
stu对象查找name属性,现在自己对象上找,找不到的话会去Student的原型对象上找,再找不到会向Obejct的原型对象中找,因为Obejct原型对象是最顶层的对象,若再找不到则会返回最终的结果。
下面这边代码还是使用上面的那段代码,只是做了一个微小的调整。
1.在我们定义Student这个构造函数后,实例化Person构造函数,实例化构造函数会返回一个实例对象,将这个实例对象赋值给Student的prototype属性,让这个属性的引用指向这个对象。
2.当我们通过new关键字实例化Student构造函数的时候,会返回一个实例对象stu,因为stu是一个对象,它会有一个隐式的原型对象,而这个隐式的原型对象的指向是有Student.prototype决定的。
3.因为我们在实例化创建stu之前,已经将Student.prototype指向了Person的实例对象p,所以stu的隐式原型对象会指向p这个对象。
// 父类:公共的属性和方法
function Person() {
this.name = 'lhq'
}
Person.prototype.eating = function() {
console.log(this.name + 'eating')
}
// 子类: 特有的属性和方法
function Student() {
this.sno = '111'
}
// 创建出Student类后做这个赋值操作
const p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}
const stu = new Student()
console.log(stu.name)
stu.studying()
// 第一个弊端: 只打印本对象上的属性,继承的属性看不到
console.log(stu)
// 第二个弊端:创建出来两个实例对象
const stu1 = new Student()
const stu2 = new Student()
// 直接修改对象上的属性,是给本对象上添加一个新属性,不会修改原型引用上的name
stu1.name = 'zhangsan'
console.log(stu2.name)
// 获取引用,修改应用中的值,会互相影响
stu1.friends.push('lisi')
console.log(stu2.friends)
// 第三个弊端:无法传递参数
注意:Student的实例对象并不是一定要指向Student默认的原型对象的,这个要看构造函数中的prototype指向谁,在创建实例对象的过程中,prototype的引用会赋值给实例对象的隐式原型对象
当Student的默认原型对象没人引用它的时候,gc会将其删除
将Perosn的实例对象赋值给Stundent.prototype后的内存引用关系图:
这个方案决解了,原型链继承存在的弊端,但是该方案也产生了一些弊端。
// 父类:公共的属性和方法
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) {
// 通过call方法调用Person对象,并传递参数,call方法的作用就是改变函数内部的this指向
// 当我们通过new Student的时候会执行这个函数的调用
Person.call(this, name, age, friends)
this.sno = sno
}
const p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}
const stu = new Student('lhq', 18, 'zhangsan', 1110)
console.log(stu)
const stu1 = new Student('lhq', 18, 'zhangsan', 1110)
console.log(stu1)
/**
* 借用构造函数存在的弊端
*
* 1.第一个弊端:Person函数至少会被调用两次 person.call() new Person()
* 2.第二个弊端:p的原型对象上会多出一些属性,但是这些属性是没有存在的必要的在我们new Person创建实例对象的时候添加
*/
实现对象和对象之间的继承。
// 原型式
const personObj = {
running() {
console.log('running')
}
}
// 工厂函数
function createStudent(name) {
const stu = Object.create(personObj)
stu.name = name
stu.studying = function() {
console.log('studying')
}
}
const stuObj = createStudent('lhq')
const stuObj1 = createStudent('zhangsan')
弊端:创建出来的对象都有一个重复的方法
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function() {
console.log('running')
}
Person.prototype.eating = function() {
console.log('eating')
}
function Student(name, age, height, sno, score) {
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
// 创建一个对象,这个对象的隐式原型指向Person的原型对象,将创建出来的对象赋值给Student的原型对象
Student.prototype = Object.create(Person.prototype)
// 将constructor的指向该回Student
// Student.prototype.constructor = Student
Object.defineProperty(Student.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: Student
})
Student.prototype.studying = function() {
console.log('studying')
}
const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)
将创建对象的函数封装:
// 工具函数
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Object.create(SuperType.prototype)
// 将Student.prototype.constructor指会Student
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function() {
console.log('running')
}
Person.prototype.eating = function() {
console.log('eating')
}
function Student(name, age, height, sno, score) {
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
// 调用工具函数
inheritPrototype(Student, Person)
Student.prototype.studying = function() {
console.log('studying')
}
const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)
低版本使用:
上面创建对象使用了Obejct.create(),低版本可能会出现问题,可以采用下面这种方案,功能是一致的。
function createObejct(O) {
function Fn() {}
Fn.prototype = O
return new Fn()
}
// 工具函数
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObejct(SuperType.prototype)
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function() {
console.log('running')
}
Person.prototype.eating = function() {
console.log('eating')
}
function Student(name, age, height, sno, score) {
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
// 调用工具函数,改变内存中的引用关系
inheritPrototype(Student, Person)
Student.prototype.studying = function() {
console.log('studying')
}
const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)
stu.running()
stu.eating()
stu.studying()
欢迎大家评论指正,共同进步~