JS中的继承指的是子类继承父类中的属性和方法,用的比较多的是原型继承,call继承,以及寄生组合继承,今天我们来简单讲解一下这三类继承。
- 原型继承
function A() { this.x = 100; } A.prototype.getX = function getX() { console.log(this.x); }; function B() { this.y = 200; } B.prototype.sum=function(){} B.prototype = new A; B.prototype.getY = function getY() { console.log(this.y); }; let b = new B;
简单分析一下上面的代码块,第一行我们定义了一个父类A,A是一个函数对象,会给他分配一个内存地址,假设为FFFAAA001,A类有一个prototype属性指向它的原型,原型也是一个对象,假设内存地址为FFFAAA002,而原型对象也有一个constructor属性指向它的构造函数A;然后第四行代码在A的原型上添加了一个getX方法,如下图
每一个内存地址里面装的都是对应的代码块,后来定义了一个子类B类,并在原型上定义了一个sum方法,如下图,
关键点来了,注意这句,B.prototype = new A,这句什么意思呢?new A我们都知道new出来的是A类的一个实例,而实例上的_proto_属性是指向类的原型的;B.prototype刚刚我们也说了,就是我们子类B的原型对象,就是我们上图中的FFFBBB002这个内存地址;由上图我们可以知道,B.prototype = FFFBBB002,但现在我们手动的改变了B.prototype的内存地址,并在B.prototype里面定义了一个getY方法,看下图
如上图所示,我们假设new A的实例的内存地址为FFFAAA003,那么原来的B.prototype指向已经从原来的FFFBBB002改为了FFFAAA003,我们可以在控制台输出看看效果
图1是没有改变B.prototype指向的时候,图2是改变了指向的,很明显,图2的原型已经指向了实例A,并且,原型上也没有了原来定义的sum方法,并且后面添加的getY方法也是添加在了A实例上,至此,我们的原型继承已经完成了。
最后我们来看一下let b = new B这行代码
我们在控制台上输出一下b,
很明显,b是B的一个实例,因为b是通过B类new出来的一个对象,因此,他可以使用B类上的属性和方法;b._proto_属性指向是A的实例,可以通过_proto_这条链子拿到A上面的属性和方法,就是我们上面画的箭头,然后A实例可以通过_proto_属性访问A.prototype,因此,b这个实例可以访问B类以及A类的属性和方法了。 -
call继承
直接上代码块function A() { this.x = 100; } A.prototype.getX = function getX() { console.log(this.x); }; function B() { //CALL继承 A.call(this); //=>this.x = 100; b.x=100; this.y = 200; } B.prototype.getY = function getY() { console.log(this.y); }; let b = new B; console.log(b);
代码没什么不同,先创建A类,在A的原型上添加getX方法,再创建B类,在B类的原型上绑定getY方法,最后创建一个B类的实例,如下图
我们都知道,继承就是子类继承父类的属性和方法,那么call继承是如何完成这一步的呢,那就是使用call来改变this指了,我们主要分析A.call(this)这句代码,在不加call时,也就是直接执行A(),那么A方法里面的this.x = 100就相当于window.x = 100,因为函数内部的this指向大多是由执行这个方法的对象决定的,执行A()相当于执行window.A(),那么this就是window,回到继承上,我们是想b这个实例能访问到A上面的方法,那么我们就得改变A里面的this指向,因此我们用call来改变this的指向,A.call(this),就是把当前调用方法B的对象(也就是b)传给A,A内部的this就变成了b,因此b.x = 100了。 -
寄生组合继承(CALL继承+变异版的原型继承共同完成)
function A() { this.x = 100; } A.prototype.getX = function getX() { console.log(this.x); }; function B() { A.call(this); this.y = 200; } //=>Object.create(OBJ) 创建一个空对象,让其__proto__指向OBJ(把OBJ作为空对象的原型) B.prototype = Object.create(A.prototype); B.prototype.constructor = B; B.prototype.getY = function getY() { console.log(this.y); }; let b = new B; console.log(b);
从代码中可以看出,在执行创建空对象之前的代码和call继承的代码一模一样,在创建了两个构造类之后,将B的原型属性指向一个空对象的原型,Object.create(A.prototype)这句代码是指创建一个空对象,并将空对象的_proto_属性指向A.prototype,也就是把A.prototype作为空对象的原型,并手动的把B的原型对象上的constructor 属性指向B本身,因为空对象本身是不具有constructor 属性的,这样子会造成constructor 属性的缺失。