js的继承在之前一直有很多疑惑,理解并不透彻,今天有时间打算做一个梳理
继承首先需要有一个父类提供继承所需要用到的方法和属性
prototype:需要共享的属性和方法放在prototype对象里面,不需要共享的放在构造函数里面
// 父类
function Parent(name) {
this.name = name; // 属性
this.say= function() {
alert("我的名字是"+this.name);
} // 方法
}
Parent.prototype.age = 12; // 给父类构造函数原型添加属性
1.原型链继承(使用prototype)
无法向父类传参
function Person(name) {
this.name = name;
}
Person.prototype = new Parent(); // 把父类实例化赋值给Person的原型,使Person原型继承父类的属性和方法;这里需要理解new这个过程都做了些什么:1. 一个新的对象被创建, **同时继承了对象类型的原型** ,即Person.prototype;2. 执行对象类型的构造函数,同时该实例的属性和方法被this所引用,即this指向新构造的实例;
var perChild = new Person("name")
console.log(perChild) //打印出来内容见图一
由图一可见perChild
console.log(perChild instanceof Parent) // true ;instanceof判断一个元算是否在另一个元素的原型链上,打印为true说明perChild在parent的原型链上。
图一
原型链继承:让实例原型继承父类的原型
**原型链继承特点:**1.实例可以继承父类构造函数得属性和方法以及原型上得属性和方法;
2.由代码结合图一可见间接继承父类的实例也就是perChild无法向父类传递参数;
3.所有实例会共享父类的属性和方法
4.一旦一个实例更改了原型上的数据,这个实例以及他下面原型链上的均会被更改如图二
Person.prototype.age = 10
console.log(perChild, Person.prototype.age, Parent.prototype.age) // 见图二
图二
2.构造函数继承(借用call或者apply)
无法继承父类原型对象
// 父类构造函数
function Parent(name) {
this.species = "人";
this.name = name;
this.say = function() {
alert("我的名字是" + this.name);
}
this.say()
}
Parent.prototype.sex = "男"
// 子类构造函数
function Person(name) {
Parent.call(this, name);
this.age = 12
}
// 在子类中使用call或者apply把父类的函数插入子类中运行
// 实例化子类函数
var Lily = new Person('Lily');
console.log(Lily instanceof Person); // true继承子类函数原型
console.log(Lily instanceof Parent); // false 没有继承父类函数的原型
console.log(Lily); // 打印见图三
图三
又上面的代码可见实例不在父类原型链上所以不能继承父类原型对象上的属性和方法;可以向父类传参;可以继承多个构造函数,多call或者apply几个父类构造函数。
构造函数继承,每一次实例都要重新调用
3.原型链+构造函数继承(call或者apply+prototype)
解决了1、2两种方式不可以传参,不可以继承prototype属性的问题,但是父类被调用了两次
// 父类
function People(name, height) {
this.name = name;
this.height = height;
}
People.prototype.sex = '女';
// 子类
function Student(name, height) {
People.apply(this, arguments); // 父类插入子类中运行
}
Student.prototype = new People(); /
// 实例
var Lily = new Student("liyi", "1.58")
console.log(Lily) // 结果见图四
图四
从打印结果可见使用apply把父类函数插入到子类运行是子类继承了父类的属性name和height;使用原型连继承把父类赋值给子类原型,使子类继承父类的原型对象(prototype),所以实例化对象继承了父类的属性也继承了父类原型对象。可传参可复用但是调用了两次父类,call或者apply使子类构造函数替代父类构造函数
4.寄生组合继承
子函数使用call继承父类自身的属性和方法,利用中间函数F继承父类的prototype属性
// student构造函数
function Student(props) {
this.name = props.name || "undefined";
}
Student.prototype.hello = function() {
alert("Hello,"+this.name+"!")
} // 给Student构造函数原型创建hello方法
// 原型链 Student -- Object --- null
// 基于Student扩展出primaryStudent
// 定义primaryStudent
function primaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 0;
}
var ann = new primaryStudent({grade: 12, name: "ann"})
// ann.hello() //报错
// console.log(ann.hello);
console.log(primaryStudent instanceof Student) // false
// 目前原型链 new primaryStudent --- primaryStudent.prototype ---object.prototype---null
// 根据上面两个打印结果可知primaryStudent并不在Student原型链上,primaryStudent并没有继承Student的hello方法,仅仅使用call方法把Student构造函数本身的属性指向了primaryStudent
// 需要达到目的:primaryStudent要在Student的原型链上
//使用一个中间函数原型指向Student.prototype, call使primaryStudent拥有了Student函数自身的属性,现在只想继承Student的原型所以要使用一个空的中间函数来转换
function F() {};
// 把F的原型指向Student
F.prototype = Student.prototype;
// 把primaryStudent原型指向F对象,F现在原型指向student
primaryStudent.prototype = new F();
var anna = new primaryStudent({grade: 12, name: "ann"});
console.log(primaryStudent.prototype.constructor) // 构造函数使Student,详情见图五
// 由于使用newF()赋值给了primaryStudent的原型,所以原型构造函数变成了F,所以这里要把构造函数转回来
primaryStudent.prototype.constructor = primaryStudent
console.log(primaryStudent.prototype.constructor) // 构造函数primaryStudent,详情见图六
// 在primaryStudent原型上构造方法
primaryStudent.prototype.getGrade = function() {
return this.grade
}
图五
图六
5.原型继承
function Parent() {
this.name = "xiaoming";
}
Parent.prototype.age = 12;
function middleFun(fun) { // 参数为要继承的对象
function F() {};
F.prototype = fun; // F原型继承fun对象的属性和原型属性
return new F(); // 输出一个对象
}
var sub = middleFun(new Parent())
console.log(sub) // 见图七
图七
6.es6-class继承
class Person {
constructor(height, width) {
this.height = height
this.width = width
}
// 构造函数属性相当于:
// function Person(height, width) {
// this.height = height
// this.width = width }
say() {
alert("hello")
}
// 相当于Person.prototype.say = function() {}
}
class Student extends Person {
constructor(height, width, name) {
super(height, width)
//super()相当于Person .prototype.constructor.call(this)
this.name = name
}
}
var lin = new Student(2, 3, "lin")
// 下面事es5的写法等价于上面es6的写法,class继承是原型继承的语法糖,写法更简单,结构更清晰
function Person(height, width) {
this.height = height
this.width = width
}
Person.prototype.say = function() {
alert("hello")
}
function Student(height, width, name) {
Person.call(this, height, width)
this.name = name
}
Student.prototype = new Person()
var lin = new Student(2, 3, "lin")