继承
继承:就是让一个对象可以具有另一个对象的特性。
每个构造函数都是一个类,可以实例化出很多具体的对象。
写一个兔子类,写一个鸡类
function Rubbit(){ // 兔子的构造函数
this.name = "兔子";
this.tui = "4条";
this.eat = "能吃";
this.pao = "能跑"
}
function Ji(){ // 鸡的构造函数
this.name = "鸡"
this.tui = "2条";
this.eat = "能吃";
this.pao = "能跑";
}
从上面代码中能看出来,兔子类和鸡类,有一些共同的特征:eat 和 pao这两个属性相同。其实,这是每个动物都能有的特征。为了节省代码,也为了描述他们的关系,我们可以写一个动物类,让兔子类和鸡类都具备动物的一些特征,也就是将它们相同的属性都拿出来放到一个动物类中。
那么,怎么才能让一个对象能访问另一个对象的属性呢? - -继承
有以下四种方式继承。
-------- 1、原型继承 -------
访问一个对象的属性或方法的时候,先在当前对象上找,找不到,就去这个对象的原型(对象)上找,找到了就能访问了。
上面我们说到,兔子的原型上如果能有 eat和pao 的属性的话,就好了,使用兔子直接就能访问原型上的 eat和pao了 。
通过一个例子来说明:
function Animal(){
this.name = "动物";
this.attribute = "能动";
}
var animal = new Animal(); // 实例化动物类,得到动物对象
function Dog(){
this.jiao = "汪汪";
}
function Pig(){
this.jiao = "哼哼";
}
Dog.prototype = animal; // 将狗类的原型赋值为动物对象
Dog.prototype.constructor = Dog; // 手动给新的原型添加constructor属性指回构造函数
var dog = new Dog();
console.log(dog.name); // 访问狗对象的name属性 - 动物
Pig.prototype = animal; // 将猪类的原型赋值为动物对象
Pig.prototype.constructor = Pig; // 手动给新的原型添加constructor属性指回构造函数
var pig = new Pig();
console.log(pig.name); // 访问猪对象的name属性 - 动物
通过上面的代码可以看出,将两者共同的属性放在一个动物类中,并将其的原型赋值为动物对象,这样狗类和猪类就都有了Animal动物类的两个属性了,然后可以通过 对象.属性 访问到。
注意!!!可以看出,我们更改了构造函数原本就自带的原型prototype,而这个prototype属性天生自带一个属性constructor,指向构造函数,一旦使用原型继承,这个原型上没有constructor属性了。我们有一个解决办法,就是使用原型继承的时候,应该手动添加constructor属性,让新的原型也指向构造函数,如上代码。
-------2、上下文调用模式继承 / 借用函数继承-------
把父类构造函数体借用过来使用一下。
function Animal(){
this.eat = "能吃";
this.pao = "能跑";
}
Animal.prototype.dong = function(){
console.log("能动");
}
function Dog(){
/*
上下文调用模式:
call
apply
bind
共同点就是能改变this的指向
call怎么改变?
可以调用函数,顺便将里面的this改成目标参数
*/
Animal.call(this); // 将父类借用一下,并将函数的this原来是父类改变成子类,这里的this指的是new Dog的对象dog
// 将Animal中的this改成了dog
// 将Animal进行调用 - 执行里面的代码了,this.eat ====> dog.eat
// this.pao ===> d.pao
this.name = "狗";
}
var dog = new Dog();
console.log(dog); // 包含了Animal中的两个属性
dog.dong(); // 调用dong方法,报错不存在这个方法
这种继承方式主要就是通过this指向来调用父类的属性和方法,那为什么调用父类原型上的dong方法会报错呢?
这就要说到借用函数继承的一个缺点了:
如果Animal这个构造函数,里面只绑定了属性,方法在Animal的原型上,但是这种继承方式不能继承原型上的方法/属性,也就是原型上的方法和属性都不能继承。
------3、组合继承------
就是把 原型继承 和 借用函数继承 两个方式组合在一起,可以解决我们上面说的借用函数继承的缺点。
那么怎么结合?
function Animal(){
this.eat = "能吃";
this.pao = "能跑";
}
Animal.prototype.dong = function(){
console.log("能动");
}
function Dog(){
// 使用借用函数继承模式
Animal.apply(this);
this.name = "狗";
}
// 使用原型继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // 手动给新的原型添加constructor属性指回构造函数
var dog = new Dog();
console.log(dog);
dog.dong(); // 调用dong方法,也能访问了
也就是讲两种继承方式都用上,就可以解决借用函数继承的缺点
---------- 4、es6的继承 ----------
在es6之前,可以说没有类这个概念,es6中新增了定义类(构造函数的方式)
语法:类 extends 父类{ }
我们讲一下在es6中定义一个类
class Animal{
}
var animal = new Animal();
这种方式定义的类,也必须和new配合使用。
如果在实例化需要传递参数的话,就在这个类中,写一个函数,名字必须是 constructor
class Animal{
constructor(name){
this.name = name;
}
}
var animal = new Animal("动物");
console.log(animal); // Animal {name: "动物"}
要给类添加方法,就像写constructor一样写一个函数,这种写法和我们以前给构造函数的原型上添加方法是一样的。
class Animal{
constructor(name){
this.name = name;
}
dong(){
console.log("能动");
}
}
var animal = new Animal("动物");
console.log(animal); // Animal {name: "动物"}
animal.dong(); // 能动
这种方式的类继承很容易,而且是固定语法:
class 子类 extends 父类{
constructor(){
super();
}
}
例:
class Animal{ // 父类
constructor(name){
this.eat = "能吃";
this.pro = "能跑"
this.name = name;
}
dong(){
console.log("能动");
}
}
class Rubbit extends Animal{ // 子类 - extends关键字并不能执行父类的constructor
constructor(name){
// 如果父类有constructor,子类这里(constructor里面)必须调用一个叫做super的方法
super(name); // 这是在执行父类的constructor
this.tui = "4条";
}
sport(){
console.log("跑的特别快");
}
}
var r = new Rubbit("兔子");
console.log(r);
这就是es6的继承方式。
总结:继承就是让一个类拥有另一个类的属性和方法。