(前言:小编也是一个刚入行的小白,所以写不出来多么高深的东西,若有错误欢迎指证或者其他的什么相互验证,但是不接受大神的鄙视,谢谢,转载请注明出路)
1:为什么需要继承?
function A() {
// 通过 this.xxx = xxx; 方式添加的才是A类的实例私有的属性或者方法;
this.text = 'A类的text';
}
A.prototype.say = function () { // 写在原型上的方法或者属性都是A类公有的属性和方法;
console.log('hello world');
};
let a = new A(); // {text: '....', __proto__: ....}
console.log(a);
a.say();
// 对象.属性名 访问对象的某个属性时,首先在私有属性中查找,而私有属性中只有一个text属性,
// 所以私有属性中没有say这个属性,接着去a所属类A的原型A.prototype 上查找公有属性和方法,
// 在A的原型上有一个say方法;此时就找到了。
// 对象.属性名 在根据原型链查找的过程中,无论是找到私有的还是公有的,只要找到就算找到,找不到返回undefined;
function B() {}
let b = new B();
b.say(); // b的私有属性没有say方法,公有属性也没有say方法,所以 b.say 得到undefined,而undefined
//不能被执行,所以会报错;
现在我们需要让b能够调用到A类上的say这个方法;
我们能够让b调用say方法的前提是,say方法要么是b的私有属性,要么是b的公有属性;此时我们需要继承来实现,让B类继承A类;
让B类继承A类,我们称B类为子类,A类为父类(超类);所谓的继承就是让子类的实例能够访问父类的属性和方法,继承就是把父类的私有属性或者公有属性变成子类的私有属性或者公有属性;
(个人理解:所谓继承就是让子类实例拥有访问父类属性方法的权利)
2.怎么继承?
-
call继承:
function A() { this.x=10; this.y=20; this.getY=()=>{ console.log(this.y); } } A.prototype.getX=()=>{ console.log(this.x); }; function B() { this.z=30; A.call(this) //call继承的关键,A类构造运行将this该为B的实例,就为B的实例添加上了A的私有属性方法 } let b=new B(); console.log(b.x,b.y,b.z); // => 10 20 30 b.getY(); // => 20 b.getX(); // => b.getX is not a function
call继承关键就在于A.call(this)这一句话,在构造函数中的this指向的是当前实例,使用call来运行A类,然后将A类的this变 为B 类的实例,这样就会让B类的实例在A类的构造函数中跑上一圈,然后B就有了A类实例该有的私有属性了,就达到了子类的实例继承了父类的私有属性方法了
美中不足:只能继续父类的私有属性方法无法继承公有的属性方法 -
原型链继承:
function A() { this.x=10; this.y=20; this.getY=function(){ console.log(this.y); } } A.prototype.getX=function(){ console.log(this.x); }; function B() { this.z=30; } // 原型继承的关键,在子类实例的原型链上挂上父类的实例(父类私有属性方法)和父类的原型(父类的公有属性方法) B.prototype=new A(); B.prototype.constructor=B; let b=new B(); console.log(b.x,b.y,b.z); // => 10 20 30 b.getY(); // => 20 b.getX(); // => 10
原型链继承的关键是 B.prototype=new A();,让子类的原型指向父类的实例,这样就会形成一个 B的实例的__proto__指向一个A类的实例,而这个A类的实例的__proto__又指向父类的原型 这样一个链式连接,使用b点属性的时候会通过原型链查找,这次父类的实例和原型都挂在子类的原型链上了,子类的实例就可以访问父类的私有以及公有属性方法了
美中不足:子类的原型丢失被父类的一个实例取代,父类的公有私有属性方法都变成了子类的公有的属性方法,所有子类的实例的原型都共享同一个超类实例的属性和方法 -
组合继承:
function A() { this.x=10; this.y=20; this.getY=function(){ console.log(this.y); } } A.prototype.getX=function(){ console.log(this.x); }; function B() { A.call(this);//call继承的关键 =》继承私有属性方法 this.z=30; } B.prototype=new A(); // 原型继承的关键 =》 继承公有属性方法 B.prototype.constructor=B; let b=new B(); console.log(b.x,b.y,b.z); // => 10 20 30 b.getY(); // => 20 b.getX(); // => 10
组合继承其实就是把call继承以及原型继承结合起来,这样就能保证父类的私有属性方法继承过来后是子类实例的私有属性方法,父类的公有属性方法是子类实例的公有属性方法,完美解决了原型继承时的让父类公私有属性方法都变为子类实例的公私有属性的问题了,但是美中不足的是:父类的私有属性方法会出现两套,子类实例一套,子类原型上还存在一套一样的属性方法
-
组合寄生继承
// Object.create() =>object类上的方法,作用是创建一个指定原型(__proto__)的对象 function A() { this.x=10; this.y=20; this.getY=function(){ console.log(this.y); } } A.prototype.getX=function(){ console.log(this.x); }; let obj=Object.create(A.prototype);//创建一个空对象,这个对象的__proto__指向A类的原型 function B() { A.call(this);//call继承的关键 =》继承父类的私有属性方法 this.z=30; } B.prototype=obj; // 继承父类的私有属性方法,但是不继承其私有属性方法 obj.constructor=B; let b=new B(); console.log(b.x,b.y,b.z); // => 10 20 30 b.getY(); // => 20 b.getX(); // => 10
这个继承就厉害了,通过Object.create(A.prototype)会创建一个空对象,但是这个空对象的__proto__指向的A的原型,然后将B的原型改为这个空对象,子类使用call继承了父类的私有属性为自己的私有属性,同时子类的__proto__指向了Object.create创建的那个空对象,其constructor是子类的构造,然后其__proto__又指向父类的原型,将父类的原型挂到子类的原型链上,这样就达到子类实例继承父类的私有属性方法为自己的私有属性方法,父类的公有属性方法为自己的公有属性方法,美中不足的是过于繁琐。
-
ES6的class类继承
class A{ constructor(){ this.x=10; this.y=20; this.getY=()=>{ console.log(this.y); } } getX(){ console.log(this.x); }; } class B extends A{ // =》extends继承父类的公有属性方法为子类的公有属性方法 constructor(){ super(); // => 这个super就相当于call继承了,让子类实例拥有父类的私有属性方法,必须写,不写会报错。 this.z=30; } } let b=new B(); console.log(b.x,b.y,b.z); // => 10 20 30 b.getY(); // => 20 b.getX(); // =>10
ES6中的这个class类继承要使用关键字extends来进行,其实这就是java语言中的继承关键字了,ES6中的很多新东西都在向java或者说在从弱语言向强语言转换,对于从java转到前端来的童鞋们这算是个福音了。
这个class继承就更加厉害了,能够完美的继承父类的私有属性方法为子类实例的私有属性方法,继承父类的公有属性方法为子类的公有属性方法,而且不用写的那么繁琐,唯一美中不足的是:ES6的兼容问题,果然好用的都不是万能的!。
还有很多其他的继承如原型式继承,寄生式继承等等,不过原理都大差不差了
附上小编学习时的画图帮助理解