ES6 class继承与super关键词深入探索

更新日期: 2019-08-23阅读: 1.9k标签: super

ES6 class

在ES6版本之前,JavaScript语言并没有传统面向对象语言的class写法,ES6发布之后,babel迅速跟进,广大开发者也很快喜欢上ES6带来的新的编程体验。
当然,在这门“混乱”而又精妙的语言中,许多每天出现我们视野中的东西却常常被我们忽略。
对于ES6语法,考虑到浏览器的兼容性问题,我们还是要把代码转换为ES5版本运行。然而,之前的ES版本为什么能模仿ES6的诸多特性,比如class与继承,super,static?JavaScript又做了哪些改变以应对这些新角色?本文将对class实例构造,class继承关系,super关键字,static关键字的运行机制进行探索。
水平有限,文中若有引起困惑或错误之处,还望指出。


class实例构造

class基本样例

基本而言,ES6 class形式如下:

 class Whatever{
      
 }

当然,还可以有constructor方法。

class Whatever{
          constructor(){
             this.name = 'hahaha';
         } 
 }

请看ES5对应版本:

function Whatever{
    this.name = 'hahaha';
}

new干了什么

可知,constructor相当于以前在构造函数里的行为。而对于ES5构造函数而言,在被new调用的时候,大体上进行了下面四步:

  1. 新建对象var _this = {};
  2. this的[[prototype]]指向构造函数的prototype,即_this.__proto_ = Constructor.prototype
  3. 改变Constructor的this到_this并执行Constructor,即Constructor.apply(_this,agrs);得到构造好的_this对象
  4. 判断Constructor的返回值,若返回值不为引用类型,则返回_this,否则返回改引用对象

所以,构造函数的实例会继承挂载在prototype上的方法,在ES6 calss中,我们这样写会把方法挂载在class的prototype:

class Whatever{
    //...
    methodA(){
        //...
    }          
}

对应ES5写法:

Whatever.prototype = function methodA(){
            //...
 }


class继承关系

原型语言基本特点

在基于原型的语言,有以下四个特点:

  1. 一切皆为对象(js中除了对象还有基本类型,函数式第一等对象)
  2. 对象皆是从其他对象复制而来(在JS对象世界中,万物始于Object.prototype这颗蛋)
  3. 对象会记住它的原型(在JS中对象的__proto__属性指向它的原型)
  4. 调用对象本身没有的属性/方法时,对象会尝试委托它的原型

看到这,大家应该明白了,为什么挂载在Constructor.prototype的方法会被实例“继承”!
在ES6 class中,继承关系还是由[[prototype]]连维持,即:

Child.prototype.__proto__ === Parent.prototype;
Child.__proto__ === Parent;
childObject.__proto === Child.prototype;

当箭头函数与class碰撞

ES6的箭头函数,一出身便深受众人喜爱,因为它解决了令人头疼的函数执行时动态this指向的“问题”(为什么加引号?因为有时候我们有时确实需要动态this带来的巨大便利)。箭头函数中this绑定在词法作用域,即它定义的地方:

//ES6:
const funcArrow = () => {
    //your code
}
//ES5:
var _this = this;
var funcArrow = function(){
    this = _this;
    //your code
}

有的童鞋可能会想到了,既然js中继承和this的关系这么大,在calss中采用词法绑定this的箭头函数,会有怎么样呢?
我们来瞧瞧。

class WhateverArrow{
        //
        methodArrow = () => {
            //...
        }          
    }

这种写法会与上文中写法有何区别?

class WhateverNormal{
        //
        methodNormal() {
            //...
        }          
    }
    

我们在chrome环境下运行一下,看看这两种构造函数的prototype有何区别:

WhateverArrow.prototype打印结果:
constructor: class Whatever1
__proto__: Object
WhateverNormal.prototype打印结果:
constructor: class Whatever2
methodNormal: ƒ methodNormal()
__proto__: Object

结合上文中关于原型的论述,仔细品味这两者的差别,最好手动尝试一下。

方法与函数类型属性

我们称func(){}的形式为“方法”,而methodArrow = () =>:any为属性!方法会被挂载在prototype,在属性不会。箭头函数methodArrow属性会在构造函数里赋值给this:

this.methodArrow = function methodArrow(){
    this = _this;
    //any code
}

在实例调用methodArrow时,调用的是自己的methodArrow,而非委托calss WhateverArrow.prototype上的方法,而这个箭头函数中this的指向,Babel或许能给我们一些启示:


 var WhateverArrow = function WhateverArrow() {
  var _this = this;

  _classCallCheck(this, WhateverArrow);

  _defineProperty(this, "methodArrow", function () {
    consoe.log(_this);
  });
};


遇见extends,super与[[HomeObject]]

让我们extends一下

当我们谈论继承时,往往指两种:

  1. 对象实例继承自一个类(构造函数)
  2. 子类继承父类

上文中我们探讨了第一种,现在,请把注意力转向第二种。
考虑下方代码:

class Parent {
    constructor(){
        this.tag = 'A';
        this.name = 'parent name'
    }
    methodA(){
        console.log('methodA in Parent')
    }
    methodB(){
        console.log(this.name);
    }
}

class Child extends Parent{
    constructor(){
        super();        
        //调用super()之后才用引用this
        this.name = 'child name'
    }
    methodA(){
        super.methodA();
        console.log('methodA in Child')
    }
}
const c1 = new Child();
c1.methodA();//methodA in Parent // methodA in Child

我们通过extends连接了两个class,标明他们是“父子关系”的类,子类中方法会屏蔽掉父类中同名方法,与Java中多态特性不同,这里的方法参数数量并不影响“是否同一种方法”的判定。
在Child的constructor中,必须在调用super()之后才能调用this,否则将会因this为undefined而报错。其中缘由,简单来说就是执行new操作时,Child的_this来自于调用Parent的constructor,若不调用super(),_this将为undefined。对这个问题感兴趣的同学可以自行操作试试,并结合Babel的转换结果,进行思考。


super来自何方?[[HomeObject]]为何物?

super干了什么

super可以让我们在子类中借用父类的属性和方法。

 methodA(){
            super.methodA();
            console.log('methodA in Child')
        }
        

super关键词真是一个增进父子情的天才创意!
值得注意的是,子类中methodA调用super.methodA()时候,super.methodA中的this绑定到了子类实例。

super来自何方?如何请到super这位大仙?

用的舒服之后,我们有必要想一想,Child.prototype.methodA中的super是如何找到Parent.prototype.methodA的?
我们知道:

Child.prototype.__proto__ === Parent.prototype;
cs.__proto__ === Child.prototype;
c1.methodA();

当c1.methodA()执行时,methodA中this指向c1,难道通过多少人爱就有多少人恨的this?
仔细想想,如果是这样(通过this找),考虑如下代码:

//以下代码删除了当前话题无关行
class GrandFather{
    methodA(){            
        console.log('methodA in GrandFather')
    }  
}
class Parent extends GrandFather{       
    methodA(){
        super.methodA();
        console.log('methodA in Parent')
    }       
}
    
class Child extends Parent{
   methodA(){
        super.methodA();
        console.log('methodA in Child')
    }
}

想想我们现在是执行引擎,我们通过this找到了c1,然后通过原型找到了Child.prototype.methodA;
在Child.prototype.methodA中我们遇见了super.methodA();
现在我们要去找super,即Parent。
我们通过this.__proto__.__proto__methodA找到了Parent.prototype.methodA;
对于Parent.prototype.methodA来说,也要像对待c1一样走这个方式找,即在Parent..prototype.methodA中通过this找其原型。
这时候问题来了,运行到Parent.prototype.methodA时,该方法中的this指向的还是c1。
这岂不是死循环了?
显然,想通过this找super,只会鬼打墙。

[[HomeObject]]横空出世

为了应对super,js引擎干脆就让方法(注意,是方法,不是属性)在创建时硬绑定上[[HomeObject]]属性,指向它所属的对象!
显然,Child中methodA的[[HomeObject]]绑定了Child.prototype,Parent中methodA的[[HomeObject]]绑定了Parent.prototype。
这时候,根据[[HomeObject]],可以准确无误地找到super!
而在Babel转为ES5时,是通过硬编码的形式,解决了对super的引用,思路也一样,硬绑定当前方法所属对象(对象或者函数):

//babel转码ES5节选
_createClass(Parent, [{
    key: "methodA",
    value: function methodA() {
        //此处就是对super.methodA()所做的转换,同样是硬绑定思路
      _get(_getPrototypeOf(Parent.prototype), "methodA", this).call(this);    
      console.log('methodA in Parent');
    }
  }]);
  

注意属性与方法的差别:

var obj1 = {
    __proto__:SomePrototype,
    methodQ(){ //methodQ绑定了[[HomeObject]]->obj1,调用super
        super.someMethod();
    }
}

var obj2 = {
    __proto__:SomePrototype,
    methodQ:function(){ //methodQ不绑定任何[[HomeObject]]
        super.someMethod();//Syntax Eroor!语法错误,super不允许在对象的非方法中调用
    }
}

箭头函数再袭super

结合前文中关于class内部箭头函数的谈论,有个问题不得不引起我们思考:class中的箭头函数里的super指向哪里?
考虑如下代码:

class Parent{       
    methodA(){       
       console.log('methodA in Parent')
    }       
}
    
class Child extends Parent{
    methodA = () => {
       super.methodA();
       console.log('methodA in Child')
    }
}

const c1 = new Child();
c1.methodA();

输出为:

methodA in Parent
methodA in Child

似乎没什么意外。我们需要更新异步,把Parent的methodA方法改为箭头函数:

class Parent{       
    methodA = () => {       
       console.log('methodA in Parent')
    }       
}
    
class Child extends Parent{
    methodA = () => {
       super.methodA();
       console.log('methodA in Child')
    }
}

const c1 = new Child();
c1.methodA();

很抱歉,人见人恨得异常发生了:

Uncaught TypeError: (intermediate value).methodA is not a function
    at Child.methodA 
    

如何把Child中的methodA改为普通方法函数呢?

class Parent{       
    methodA = () => {       
       console.log('methodA in Parent')
    }       
}
    
class Child extends Parent{
    methodA () {
       super.methodA();
       console.log('methodA in Child')
    }
}

const c1 = new Child();
c1.methodA();

输出:

methodA in Parent
//并没有打印methodA in Child

以上几种结果产生的原因请结合前几章节细致品味,你会有所收获的。


不容忽视的static

static的表现

简单来说,static关键词标志了一个挂载在class本身的属性或方法,我们可以通过ClassName.staticMethod访问到。

class Child{
    static name = '7788';    
    static methodA () {       
       console.log('static methodA in Child')
    }
}
Child.name;//7788;
Child.methodA();//static methodA in Child

static如何传给子类

因为Child本身的[[prototype]]指向了Parent,即Child.__proto__===Parent 所以,static可以被子类继承:

class Parent{       
    static methodA () {       
       console.log('static methodA in Parent')
    }     
}
    
class Child extends Parent{
    
}

Child.methodA();//static methodA in Parent

static方法中访问super

class Parent{       
    static methodA () {       
       console.log('static methodA in Parent')
    }     
}
    
class Child extends Parent{
    static methodA () {   
        super.methodA()    
       console.log('static methodA in Child')
    }  
}

Child.methodA();
//输出:
//static methodA in Parent
// static methodA in Child


结语

JS是门神奇的语言,神奇到很多人往往会用JS但是不会JS(...hh)。作为一门热门且不断改进中的语言,由于跟随时代和历史遗留等方面的因素,它有很多令人迷惑的地方。
在我们每天面对的一些特性中,我们很容易忽视其中机理。就算哪天觉得自己明白了,过一段时间可能又遇到别的问题,突然觉得自己懂得还是太少(还是太年轻)。然后刨根问底的搞明白,过一段时间可能又。。。或者研究JS的历程就是这样螺旋式的进步吧。
感谢Babel,她真的对我们理解JS一些特性的运行机理非常有用,因为Babel对JS吃的真的很透彻(...)。她对ES6的“翻译”,可以帮助我们对ES6新特性以及往前版本的JS的理解。
行文匆忙,难免有错漏之处,欢迎指出。祝大家身体健康,BUG越来越少。

原文:https://segmentfault.com/a/1190000020167455

链接: https://www.fly63.com/article/detial/4901

ES6 类继承 和 super的使用

ES6中继承的子类中,如果使用构造函数constructor()那么就必须使用 super()方法初始化,这样下面才可以调用this关键字。super()只能用在子类的构造函数之中,用在其他地方就会报错。

谈谈super(props) 的重要性

我听说 Hooks 最近很火。讽刺的是,我想用一些关于 class 组件的有趣故事来开始这篇文章。你觉得如何?本文中这些坑对于你正常使用 React 并不是很重要。 但是假如你想更深入的了解它的运作方式,就会发现实际上它们很有趣。

js中的super的使用

super()相当于Parent.prototype.constructor.call(this)ES5的继承,实质上是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.call(this)). ES6的继承

为什么我们要写 super(props) ?

据说 Hooks 势头正盛,不过我还是想略带调侃地从 class 的有趣之处开始这篇博客。可还行?这些梗对于使用 React 输出产品并不重要

jses6语法:class类 class继承 super关键字

Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承,super关键字既可以当做函数使用,也可以当做对象使用,当做函数使用的时候,代表的是父类的构造函数

es6中class类、super和estends关键词

JavaScript 语言在ES6中引入了 class 这一个关键字,在学习面试的中,经常会遇到面试官问到谈一下你对 ES6 中class的认识,同时我们的代码中如何去使用这个关键字,使用这个关键字需要注意什么,这篇来总结一下相关知识点。

面试官:this和super有什么区别?this能调用到父类吗?

this 和 super 都是 Java 中的关键字,都起指代作用,当显示使用它们时,都需要将它们放在方法的首行(否则编译器会报错)。this 表示当前对象,super 用来指代父类对象

ES6中class方法及super关键字

记录下class中的原型,实例,super之间的关系,构造器中的this指向实例对象,在构造函数上定义的属性和方法相当于定义在类实例上的,而不是原型对象上

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!