为了让子类继承父类的属性(也包括方法),首先需要定义一个构造函数。然后,将父类的新实例赋值给构造函数的原型。代码如下:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(`我的名字是${this.name}`);
}
function Student(name,stuno) {
this.name = name;
this.stuno = stuno;
}
let pp = new Person('彭昱畅')
let why = new Student('哈哈',10001);
console.log(pp);
console.log(why);
看上面的这段代码,我在里面定义了两个构造函数,分别是Person
和 Student
,并且在 Person
构造函数的原型上添加了 sayName
方法.
此时此刻,Person
的实例化对象 pp
能够调用到这个方法,但是Studnet
的实例化对象 why 并不能访问到.
用下面的一张图上面代码中涉及的来展示原型链:
但是我们现在有一个需要,就是我们希望 Student
的实例化对象 why
也能够执行 sayName
方法,该怎么办呢?
在原型链中我们有讲到,原型链的查找,其实和我们的作用域查找非常的相似,会一直想上级作用域查找,直至查找到全局.从上面的一张图中我们可以明显的看到在实例化对象 why
的原型链上并不能查找到 sayName
方法,所以现在我们就要改变 why
的原型链,让它的原型链上也有 sayName
方法.
why
是构造函数 Student
的实例化对象,只要 Student
的原型发生了变化,那么它的实例化对象 why
的原型对象也会发生变化,所以我们要做的就是让构造函数 Student
的原型指向构造函数 Person
的原型,如下图:
这样只需要在原先的代码基础上添加下面的一行代码就可以实现修改原型链的需求:
Student.prototype = Person.prototype;
现在 Student
的实例化对象 why
也可以访问到 sayName
方法.
到这里其实我们就已经实现了原型链继承.
但是我还有更进一步的需求,我们需要 Student
构造函数实例化出来的对象有一个 sayStuno
方法,按照我们之前的方法,我们需要给 Student
的原型上添加这个方法.如下图:
但是现在又有问题出现了,如果我们这样做的话,Person
的实例化对象也就有了 sayStuno
方法,这样 两个构造函数的实例就都能访问 sayStuno
方法了,但是因为实例化对象 pp
并没有学号,所以打印的结果是 undefined
.
所以我们要将 sayStuno
方法 ‘私有化’ ,就是只能够通过 Student
的实例化对象访问到,而其他的方法访问不到.
现在我们要构造函数 Student
的实例化对象和 Person
的实例化对象都能访问到 sayName
方法,但是 sayStuno
方法要求只能 Student
访问.
此时我们就可以用构造函数 Person
来实例化一个对象,这个对象里面存储的就是我们需要的 sayStuno
方法.然后让 Student
的原型指向这个函数,这样做的目的就是为了 Student
的实例化对象 why
在进行原型链查找的时候可以找到 sayStuno
方法,而且 pp
查找不到.
办法如下面的一张图所示:
实现的代码很简单,就只需要添加下面的代码即可:
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的学号是${this.stuno}`);
}
此时我们分别用两个实例化对象访问 sayStuno
方法,结果如下:
二者依旧可以访问 sayName
,但是 pp
不可以访问 sayStuno
,why
可以访问 sayStuno
原型链查找过程:
完整的代码如下:
function Person(name) {
this.name = name;
}
let pp = new Person('彭昱畅')
Person.prototype.sayName = function () {
console.log(`我的名字是${this.name}`);
}
function Student(name,stuno) {
this.name = name;
this.stuno = stuno;
}
////核心语句,继承的实现全靠这条语句了:
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的学号是${this.stuno}`);
}
let why = new Student('哈哈',10001);
console.log(pp);
console.log(why);
其实原型链继承说白了就是与自己本身的原型链切断联系,继承其他构造函数的原型.
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
我们先来观察下面的一段代码:
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,stuno) {
this.name = name;
this.age = age;
this.sex = sex;
this.stuno = stuno;
}
let pp =new Person('彭昱畅',18,'男');
let why = new Student('哈哈',13,'女',10001)
console.log(pp)
console.log(why);
对于上面这段代码,你品,你细品!!!
有没有发现一个问题,Person
构造函数和 Student
构造函数也太像了叭! Student
就只比 Person
多了一个参数, 身为懒癌晚期患者,如果可以肯定是不想敲这么多重复的代码的,所以就有了下面一个办法.
既然二者之间那么像,那我能不能直接在 Student
构造函数中调用 PErson
构造函数呢.当然是可以的啦.
function Stdeunt(name,age,sex,stuno) {
Person(name,age,sex);
this.stuno = stuno;
}
这个时候我们只需要解决一个问题,那就是 this
的指向问题,如果我们不做任何的更改,直接调用的话,那么当函数的执行权进入 Person
函数的时候,this
就会指向 window
,而不是我们要实例化的对象,所以我们只需要下面这样做就可以实现构造函数的继承liao…
不了解call的用法的可以戳链接哦https://blog.csdn.net/lhrdlp/article/details/104320665
完整的代码如下哦:
//父类
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//子类
function Student(name,age,sex,stuno) {
// 核心语句
Person.call(this,name,age,sex);
this.stuno = stuno;
}
let pp =new Person('彭昱畅',18,'男');
let why = new Student('哈哈',13,'女',10001)
console.log(pp)
console.log(why);
这样做也有缺点哦:
方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
就是将原型链继承和构造函数继承组合在一起;继承两个优点
其背后的思路是 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的学号是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱畅',18);
这个时候我们进行如下的测试:
是不是已经实现了我们的需求,创建对象,构造函数 Student
的实例化对象和 Person
的实例化对象都能访问到 sayName
方法,但是 sayStuno
方法要求只能 Student
访问.
如果前面两种继承已经明白了,那么组合继承就没有一点点难度了,画了下面的图辅助理解.
但是这样做会带来一个问题,就是我们的Person
构造函数会执行两次,一次是创建子类型的时候,另一次是在子类型构造函数的内部.那么下面要介绍的寄生组合继承就解决了这个问题.
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
//核心代码
function fn(){}
fn.prototype = Person.prototype;
Student.prototype = new fn();
Student.prototype.sayStuno = function () {
console.log(`我的学号是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱畅',18);
在这里我们创建了一个新的构造函数fn
,通过之前的学习我们已经了解到函数 fn
肯定会有他自己的原型,但是我们在这里改变了他的原型.
让 fn
的原型和 Person
的原型指向同一个对象,然后用构造函数 fn
实例化一个新的空对象,让构造函数 Student
的原型指向 这个空对象.
如下图:
做如下验证:
圣杯模式就是借用构造函数封装
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
//圣杯模式核心代码
function inhert(parent,child) {
function fn() {};
fn.prototype = parent.prototype;
child.prototype = new fn();
child.prototype.constructor = Student;
child.prototype.parent = Person;
}
inhert(Person,Student);
Student.prototype.sayStuno = function () {
console.log(`我的学号是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱畅',18);