概念解析:
三个类
- 超类型
- 父类型
- 子类型
继承:就是让子类拥有父类的资源
问题:子类默认无法访问父类的任何对象
解决方案:修改原型的指向
继承的方式:
- 原型链的继承
- 借用构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生式组合继承
- 拷贝属性继承
继承的意义:
- 减少代码冗余
- 方便统一操作
- 弊端:耦合性比较强 ,比如说其中一环出了问题,整个链条都瘫痪掉了
每个函数都能构建出一个对象,这个对象内部有个属性指向着这个函数的原型对象
原型对象本质上也是一个对象,也是由另一个构造函数构造出来的,也指向那个构造函数的原型对象
以上,形成一个链式的结构,就称为原型链
原型链继承:
var arr = [1,2,3];
console.log(arr.constructor.name); //Array
console.log(arr);
console.log(arr.__proto__.constructor.name); //Array
console.log(Array.__proto__.constructor.name); //Function
console.log(Function.__proto__.constructor.name); //Function 继承顶层
console.log(Function.prototype.__proto__.constructor.name); //Object
console.log(Object.prototype.__proto__); //null
console.log(Object.__proto__.constructor.name); //Function
console.log(Array.prototype.__proto__.constructor.name); //Object
一、 让子类拥有父类的资源
//构造函数Person
function Person() {
this.name = '张三';
this.pets = ['小煤球','花花'];
}
Person.prototype.run = function () {
console.log('跑');
}
//构造函数Student
function Student() {
this.num = 'aaaa.com';
}
var stu = new Student();
//两个函数没有关系
console.log(stu);
console.log(stu.num);
console.log(stu.name);
console.log(stu.pets);
问题:子类默认无法访问父类的任何对象
解决方案:修改原型的指向比较方便
二、修改原型的指向
//构造函数Person
function Person() {
this.name = '张三';
this.pets = ['小煤球','花花'];
}
Person.prototype.run = function () {
console.log('跑');
}
//构造函数Student
function Student() {
this.num = 'aaaa.com';
}
//让Student的原型对象指向Person的原型对象
Student.prototype = Person.prototype;
var stu = new Student();
//两个函数没有关系
console.log(stu);
console.log(stu.num);
stu.run();
问题:类型问题,指针指向父类构造函数
解决方案:修复constructor指针即可
三、 构造父类的实例,并设置为子类的原型对象
//构造函数Person 父类
function Person() {
this.name = '张三';
this.pets = ['小煤球','花花'];
}
Person.prototype.run = function () {
console.log('跑');
}
//构造函数Student 子类
function Student() {
this.num = 'aaaa.com';
}
//1.构造父类的实例
var p = new Person();
//2.并设置为子类的原型对象
Student.prototype = p;
//3.修复constructor指针
Student.prototype.constructor = Student;
var stu = new Student();
//两个函数没有关系
console.log(stu);
console.log(stu.num);
stu.run();
console.log(stu.name);
console.log(stu.pets);
console.log(stu.constructor.name); //Person
问题:无法访问到父类的对象属性
思路:怎样才能拥有父类的实例属性和原型属性? 构造父类的实例,只要在原型对象上,肯定都会被共享
解决方案:添加到对象自己身上,在构造函数内部添加
优化解决方案:可以直接调用父类构造函数,但是需要修改this的指向
再优化:注意覆盖关系,如果产生重名,应该子类覆盖父类,借助构造函数继承,在子构造函数内部调用父构造函数
四、借助构造函数继承,在子构造函数内部调用父构造函数
//构造函数Person 父类
function Person() {
this.name = '张三';
this.pets = ['小煤球','花花'];
}
Person.prototype.run = function () {
console.log('跑');
}
//构造函数Student 子类
function Student() {
//借助构造函数继承,在子构造函数内部调用父构造函数
//问题:父类构造函数的参数无法修改
//注意:从此处开始已经覆盖了原型链继承和借助构造函数继承,称之为 '组合继承' = 原型链 + 借助构造函数
Person.call(this);
this.num = 'aaaa.com';
//尝试1:在构造函数内部添加,把父类的属性复制到子类身上,对应的实例化的对象也会有这些属性
// this.name = '张三';
// this.pets = ['小煤球','花花'];
//尝试2:直接调用父类构造函数,但是需要修改this的指向
// Person.call(this);
}
//1.构造父类的实例
var p = new Person();
//2.并设置为子类的原型对象
Student.prototype = p;
//3.修复constructor指针
Student.prototype.constructor = Student;
var stu = new Student();
var stu2 = new Student();
问题:父类构造函数的参数无法修改
注意:从此处开始已经覆盖了原型链继承和借助构造函数继承,称之为 '组合继承' = 原型链 + 借助构造函数
解决方案:父类构造函数需要设置接收可变参数,子类构造函数在调用父类构造函数的时候,传递参数即可
解决方案:寄生组合型继承
五、完美继承:寄生组合型继承
//构造函数Person 父类
function Person(name,pts) {
this.name = name;
this.pets = pts;
}
Person.prototype.run = function () {
console.log('跑');
}
//构造函数Student 子类 //借助构造函数继承
function Student(num,name,pts) {
//注意:一定要放在最前面
Person.call(this,name,pts);
this.num = num;
}
//1.寄生式继承
function Temp() {} //空的继承函数,作用就是用来寄生
//2.找到需要继承的原型对象
Temp.prototype = Person.prototype;
//3.让子类构造函数的原型对象为寄生构造函数的实例化对象
var stuPrototype = new Temp();
Student.prototype = stuPrototype;
//4.让寄生构造函数的原型对象的真实类型指向子类构造函数
stuPrototype.constructor = Student;
</script>
<script>
var stu = new Student('001','张三',['小花']);
var stu2 = new Student('002','李四',['小猫']);
console.log(stu);
console.log(stu2);
console.log(new Person());
</script>
总结:
针对于父类构造函数的实例属性:
使用借助构造函数继承的方式,在子构造函数中,调用父构造函数
!注意:
修改指针,放在子类构造函数内部最前面
针对于父类构造函数的原型对象属性:
- 原型链继承
- 寄生式组合继承