大家好,我是小庄。今天就来梳理一下ES6中的继承。顺便记录一下比较容易忘记的知识点。1、extends关键字其实继承的关键是extends:classmyClass{}classchildrenextendsmyClass{}分析:上面通过extends继承了myClass的所有属性和方法。2.super关键字super关键字在类中有两种完全不同的表现形式:1.表示函数时,ES6要求子类的构造函数必须执行一次super函数。表示父类的构造函数。super()作为一个函数,只能在子类的构造函数中使用,在其他地方使用会报错。A类{}B类扩展A{constructor(){super();}}上面的super虽然是A的构造函数,但是它返回的是子类B的实例。也就是说super里面的this是指B的实例。上面这行在代码中解释为:A.prototype.constructor.call(这)。2.当用super作为对象来表示一个对象时,在一个普通的方法中,它指向父类的原型对象;在静态方法中,它指向父类。虽然个人觉得这样的设计无疑会增加程序员理解super的难度,但是熟悉一下还是不错的。要区分什么是原型对象和对象本身。A类{构造函数(){this.a=2;}p(){返回2;}}B类扩展A{constructor(){super();console.log(super.p());//2}getm(){返回super.a;//undefined}}letb=newB();b.m;**super.p()显然是作为对象使用的,这里指的是父类的原型对象,p方法定义在原型上A的,所以返回值为2。当然调用b.m是取不到这个值的。这也从侧面说明:constructor()方法一般只有A本身,类中的一般方法都定义在类的原型对象上。**让我们看另一个例子:classA{constructor(){this.x=1;}print(){console.log(this.x);}}B类扩展A{constructor(){super();这个.x=2;}m(){super.print();}}letb=newB();b.m()猜猜最后会输出什么,1还是2?从上面的内容推断,调用了print方法,而print方法定义在类A中,那么打印出来的值应该是1吗?答案是2,这个JS函数的执行范围是相关的。我们应该怎么理解呢?当函数执行时,会生成一个作用域。当它运行到b.m()时,将创建一个属于m的上下文。m中的this指向它自己。调用print时,引擎会先在print里面找到this.x。如果没有找到,再往上一层操作,会找到m。如果在m中没有找到,则转到m定义的上层。如果没有找到,就一层层往上找,会发现构造函数中有一个变量x,停止搜索。如果稍微更改上面B的代码:classBextendsA{constructor(){super();}m(){super.print();}}letb=newB();b.m()此时,答案是1。因为B继承了A,所以B的原型指向了A。在B中找不到,就找在A中,所以答案是1。我们再看一个例子来加强记忆:classA{constructor(){this.x=1;}}B类扩展A{constructor(){super();这个.x=2;超级.x=3;安慰。日志(超级X);//未定义的console.log(this.x);//3}}letb=newB();什么是打印值?有同学自信地说是1,答案错了。在子类B中调用super()时;还记得它的意思吗?A、原型。构造函数。调用(这个)。是的,super.x=3;实际上指的是子类B中x的赋值,相当于this.x=3。当输出super.x时,执行A.prototype.x。而A的原型上没有x,所以会输出undefined。虽然这看起来有点绕口,但还是有道理的。显然第二个输出值是3,补充代码理解:classA{constructor(){this.x=1;}test(){console.log('这是一个测试');}y=2}classBextendsA{constructor(){super();这个.x=2;//super.x=3,其实是指子类B中x的赋值,相当于this.x=3super。x=3;//super.test=functiontestFun(){},相当于this.test=functiontestFun(){}super.test=functiontestFun(){console.log('在这里执行测试');}//super.testB=function(){},相当于this.testB=function(){}super.testB=function(){console.log('这里是修改后的testB函数');}console.log(super.test());//'这是一个测试'console.log(super.y);//undefinedconsole.log(super.x);//未定义的console.log(this.x);//3}testB(){console.log('这是testB函数');}}letb=newB();b.test();//'在这里执行测试'b。测试B();//'HereisthemodifiedtestBfunction'3.类的prototype属性和__proto__属性又到了迂回链接,熟悉一下那句话就好了。虽然我觉得这个设计真的不是那么合理。类是作为构造函数使用的,所以拥有prototype属性是理所当然的事情。那么__proto__属性从何而来?JS中的一切都是对象。虽然不准确,但是类确实是一种对象,所以__proto__来了。有人把__proto__称为隐式原型,其实是不合适的。proto__作为JS原型链的桥梁,但是这个属性在不同的浏览器中暴露的程度不同,谷歌可以访问对象的__proto。关于原型链的问题留待后面单独说明。回到类,类既有prototype属性又有__proto__属性,所以类的原型链是这样的:classA{}classBextendsA{}B.__proto__===A//trueB。prototype.__proto__===A.prototype//true你大概可以看出prototype和__proto__之间的区别。这两条继承链可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为构造函数,子类(B)的原型对象(原型属性)是父类的原型对象(原型属性)的实例。由此可见:子类__proto__的原型就是父类,那么子类的原型的原型就是父类的原型。4.补充关注公众号:【深度漂移程序员小庄】,里面有丰富的学习资源和面试经验(不限于前端,java,算法),还有学习交流群可以加,还有各大厂老板们可以一起交流学习,共同进步~加小庄微信,回复【加群】,即可加入互联网技术交流群。本文由mdnice多平台发布
