JS继承
首先我们先说说面向对象,编程范式主要分为面向对象和面向过程两种思想,JS中,我们用面向对象的方式编码。面向对象的编程思想:根据需求分析对象,找到对象有什么特征和行为,通过代码的方式来实现需求,要想实现这个需求,就要创建对象,要想创建对象就应该先有构造函数,然后通过构造函数来创建对象,然后再通过对象来调用对应的属性和方法来实现相应的需求和功能即可。
面向对象的特征:封装,继承,多态
继承和原型是为了数据共享,节省内存空间
ES5中的继承:
继承概念:子类继承父类以后,父类的所有属性和方法子类自动的都拥有了,
优点:代码复用
缺点:好的继承关系,关系清晰,可读性高,但可能出现复杂的继承关系,使代码复杂化。
第一种:通过改变原型指向实现继承:
例子:
//练习继承:
//车子:轮子,方向盘,可以跑,可以载东西
//机动车:轮子,方向盘,可以跑,可以载东西,引擎
//汽车:轮子,方向盘,可以跑,可以载东西,引擎,品牌
//ES5
function Vehicle(wheel, director) {
this.wheel = wheel;
this.director = director;
}
Vehicle.prototype.run = function () {
console.log(this.brand,"running")
};
Vehicle.prototype.load = function () {
console.log(this.brand,"loadable");
}
//Motor
function Motor(engin) {
this.engin = engin;
}
Motor.prototype = new Vehicle("轮子", "方向盘")
//Car
function Car(brand) {
this.brand = brand;
}
Car.prototype = new Motor("engine")
const car = new Car("Range Rover")
console.log(car);
console.log(car.age);
car.run();
car.load();
这是ES5中通过改变原型指向实现继承,但是这种继承方式有缺陷:改变原型指向的同时直接初始化了属性,继承过来的属性的值都是一样的,只能重新调用对象的属性重新赋值。
解决方案,继承的时候不用改变原型的指向,直接调用父级的构造函数的方式来为属性赋值就可以了(用call改变this指向来借用构造函数)
function Vehicle(wheel, director) {
this.wheel = wheel;
this.director = director;
}
Vehicle.prototype.run = function () {
console.log(this.brand, "running")
};
//Motor
function Motor(wheel, director, engin) {
Vehicle.call(this, wheel, director)
//Vehicle.apply(this, [wheel, director])
//Vehicle.bind(this)( wheel, director)
//当然这里除了使用call还可以使用apply和bind,但是要注意他们的区别
this.engin = engin;
}
const motor = new Motor("motor1", "dire1", "rngine1")
const motor2 = new Motor("motor2", "dire2", "rngine2")
console.log(motor); // {wheel: "motor1", director: "dire1", engin: "rngine1"}
console.log(motor2) // {wheel: "motor2", director: "dire2", engin: "rngine2"}
上面的方法确实也能实现继承,但是新的问题来了,由于是在构造函数内部借用别的构造函数,所以原型上的方法是无法继承的,因此用motor调用Vehicle的run方法是找不到的,报错motor.run is not a function。解决方法,组合继承:原型继承+借用构造函数继承
function Vehicle(wheel, director) {
this.wheel = wheel;
this.director = director;
}
Vehicle.prototype.run = function () {
console.log(this.wheel, "running")
};
//Motor
function Motor(wheel, director, engin) {
Vehicle.bind(this)( wheel, director)
this.engin = engin;
}
Motor.prototype = new Vehicle();
const motor1 = new Motor("motor1", "dire1", "engin1")
const motor2 = new Motor("motor2", "dire2", "engin2")
console.log(motor, motor2);
motor1.run() //motor1 running
motor2.run() //motor2 running
这样就实现了属性和方法的共同继承。
ES6中的继承
ES6给我们提供了更加方便的继承方法,class关键字生成类,子类通过extends来继承父类,让我们操作更加简单:
//ES6中的class实现继承
class Vehicle {
constructor(wheel, director) {
this.wheel = wheel;
this.director = director;
};
run() {
console.log(this.director, "running")
};
load() {
console.log(this.director, "loading")
};
}
class Motor extends Vehicle {
constructor(wheel, director, engine) {
super(wheel, director);
this.engine = engine;
}
}
// (new Motor(1,"motor")).load()
const motor = new Motor(1, "motor")
motor.load()
class Car extends Motor {
constructor(wheel, director, engine, brand) {
super(wheel, director, engine);
this.brand = brand
}
}
let car = new Car("轮子", "方向盘", "engine", "Range Rover")
console.log(car);
car.run()
car.load()
输出结果如下图所示
这样的继承是很简单的,通过上面的结果分析:在访问一个对象身上的属性时,会首先在自身找,找不到就会通过自身的proto属性一直往自己的父级找,比如我们在new Car()这个实例对象自身找不到engine属性,就会在自身的proto属性里面找,因为自身的属性指向的是构造函数(Car)的prototype,而我们已经将Car的prototype指向改为了Motor的实例new Motor();因为而这个实例自身是有engine属性的,因此Car的实例也能继承到engine属性,那么对应的Car和Motor自身没有wheel, director属性,但由于Motor的prototype指向了Vehicle的实例,所以他们的实例都可以通过原型链访问到wheel, director属性。
如果一直找到Object都没有这个属性或者方法,就返回undefined。因此new Car().number返回的undefined。