JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

14 | super.xxx():虽然直到ES10还是个半吊子实现,却也值得一讲

super.xxx如果是属性,绑定this的作用
super引用的动态查找与静态声明的矛盾
x = super.xxx.bind(...)的处理
如果在类的声明头部没有声明extends,那么在构造方法中也就不能调用父类构造方法
super实际上是在通过原型链查找父一级的对象
super.xxx()是对super.xxx这个引用(SuperReference)作函数调用操作
只能在方法中使用super
限制:在调用父类构造方法之后才能使用super.xxx
this值是从当前环境中绑定给super的
this值与super关键字没有直接关系
super指向父类的方法或属性
super只能在方法中使用
解决父类能力丢失问题
ECMAScript 6提出super关键字解决继承问题
问题:子级对象覆盖父级对象方法导致父类能力丢失
原型继承的概念
JavaScript 1.0的面向对象模型
思考题
知识回顾
super.xxx()中的this值
super关键字的作用
从JavaScript 1.0到ES6
JavaScript中的super关键字

该思维导图由 AI 生成,仅供参考

你好,我是周爱民,接下来我们继续讲述 JavaScript 中的那些奇幻代码。
今天要说的内容,打根儿里起还是得从 JavaScript 的 1.0 谈起。在此前我已经讲过了,JavaScript 1.0 连继承都没有,但是它实现了以“类抄写”为基础的、基本的面向对象模型。而在此之后,才在 JavaScript 1.1 开始提出,并在后来逐渐完善了原型继承。
这样一来,在 JavaScript 中,从概念上来讲,所谓对象就是一个从原型对象衍生过来的实例,因此这个子级的对象也就具有原型对象的全部特征。
然而,既然是子级的对象,必然与它原型的对象有所不同。这一点很好理解,如果没有不同,那就没有必要派生出一级关系,直接使用原型的那一个抽象层级就可以了。
所以,有了原型继承带来的子级对象(这样的抽象层级),在这个子级对象上,就还需要有让它们跟原型表现得有所不同的方法。这时,JavaScript 1.0 里面的那个“类抄写”的特性就跳出来了,它正好可以通过“抄写”往对象(也就是构造出来的那个 this)上面添加些东西,来制造这种不同。
也就是说,JavaScript 1.1 的面向对象系统的设计原则就是:用原型来实现继承,并在类(也就是构造器)中处理子一级的抽象差异。所以,从 JavaScript 1.1 开始,JavaScript 有了自己的面向对象系统的完整方案,这个示例代码大概如下:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

ECMAScript 6引入了关键字"super"来解决JavaScript面向对象模型中继承能力的不足。通过super关键字,子类能够完整继承父类的能力,填补了JavaScript继承机制的不足。super关键字只能在方法中使用,通过方法的主对象来找到父类,使得对象方法可以引用到父级原型中的方法。在调用父类构造方法之后才能使用super.xxx的方式来引用父类的属性或方法。此外,文章还探讨了super()中的父类构造方法和构造方法不是静态的问题,以及对super引用的动态查找和静态声明之间的矛盾。总的来说,本文深入探讨了JavaScript中的继承机制,为读者提供了对JavaScript面向对象模型的深入了解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • 穿秋裤的男孩
    读后感 1. super的出现是为了解决子类同名属性覆盖父类的属性,通过super可以直接访问到父类。 2. 伪代码:key[[HomeObject]].__proto__ === super,不知道对不对

    作者回复: 是的。👍

    2020-01-07
    4
  • 小毛
    老师,请教个问题,为什么ECMAScript 6中的class定义里方式是prototype原型方法,而属性又是实例属性呢,这样有点诡异 class A { x=1 } class B extends A { constructor() { super(); this.y = super.x + 1; } } let b = new B; console.log(b.x);//1 console.log(b.y);//NAN

    作者回复: ES6的class声明并不支持一般属性(使用数据描述符的属性),而只支持存取器属性。所以你的例子,其实是在较高版本的ECMAScript规范中才支持的。 而ES6不支持在类声明中使用一般属性,是因为那个时候ECMAScript规范在有关“类继承设计”方面并没有做完,是个半吊子设计。所以有关super.xxx这样的属性存取,在操作父类方法时是没问题的,但在操作父类属性时,就会出BUG。这个是ECMAScript规范中已知的,并且在test262中已经认可的。但——就目前而言——这个Bug还不会被修复。因为“在类声明中使用属性”的相关提案一直悬而未决。 回到你的例子,问题是 super.x 访问正好撞上了上面的bug。并且关于这个bug和相关的“类声明中的属性”目前没有定论,所以我也不能告诉你有效的处理方法。简单地说,尽量不要在类声明中使用属性声明,也不要试图通过super.xxx来访问父类的属性(但可以访问方法和使用读写器的属性)。

    2020-03-20
    3
  • 行问
    实话实说,对 Class 和 super 的知识概念还不熟悉,有几个问题请教下老师 1、在“继承”上,xxx.apply() 和 xxx.call() 算是继承吗?与 super.xxx() 又有什么区别? 2、super.xxx() 的 this 引用是在当前环境的上下文中查找的。那么,x = super.xxx.bind(...) 绑定的 this 是从父类 or 祖先类“继承”而来,这与 constructor() 中直接调用 super() 是否一致? 另,大师可以适当添加一些代码 or 图片 or 思维导图,在阅读理解上可以帮助我们更好理清,感谢!

    作者回复: 第一个问题,它们与super.xxx没什么关系,它们自己也不算继承。xxx.apply/xxx.call就是普通的函数调用,而super.xxx是先查找super,然后使用当前环境中的this来调用super.xxx()。 第二个问题,super()与super.xxx()其实很不相同,它们是分别独立实现的。super()相当于get_super(current_constructor).call();而如果是在一般的、非静态声明的方法中,super.xxx()倒是与get_super(current_constructor).prototype.xxx.bind(current_this, ...)有些类似。——注意这两种情况下的current_constructor,是等同于当前正在声明的类的。 关于图片和思维导图这类,这次极客时间的课程里面,真的没做什么。不过仅是说今天这一讲的话,可以看看之前我讲过的《无类继承》,今天的许多内容都可以看到更详细的介绍。在这里:https://v.qq.com/x/page/d0719xls8eb.html 或者看搜狐的,还有PPT: http://www.sohu.com/a/258358348_609503

    2019-12-16
    3
  • 蛋黄酱
    二刷了,来回又看了三遍左右才理解老师表达的是什么。特来评论一下。 老师的内容真的很好,但是我强烈觉得老师的语言表达需要提高,很多地方其实本身并不复杂,但是因为老师的表述具有强烈的迷惑性,绕了一个大弯,把人给绕晕了,最后并不知道你在讲什么。 实际上你看楼上@穿秋裤的男孩 的总结就及其简洁好懂。

    作者回复: 多谢。确实是很绕的。:( 注意到你已经读到14讲了,但其实正确的顺序应该是读到第6讲就看加餐1~2,而读到第11讲就读加餐3。这个我问问编辑能不能调一下顺序,至少他们现在这么排列,不利于读者学习~~ 你现在正好应该把三个加餐一并读了。所以……不妨试试看?

    2020-03-28
    2
    2
  • 油菜
    “在 JavaScript 中,super 只能在方法中使用” ---- 老师,我的测试问题在哪呢? function F1(){ this.a1='aaa'; this.b1='bbb'; this.f1=function(){ console.log('f1f1') } } class S1 extends F1{ k1(){ super.f1(); //调用父类了f1()方法 } } new S1().f1(); // f1f1 new S1().k1(); // Uncaught TypeError: (intermediate value).f1 is not a function

    作者回复: 被super.xxx访问的方法必须是原型方法,而不能是实例方法。例如: ``` F1.prototype.f1 = function() { console.log('HI') } // 例1:通过this.f1()访问到了实例方法 (new S1).f1(); // f1f1 // 例2:通过super访问到了原型方法 (new S1).k1(); // HI ```

    2020-11-17
    2
    1
  • 海绵薇薇
    老师好,我理解的基于类构造对象的过程如下: 类的构造过程是在继承的顶层通过new.target拿到最底层的构造函数, 然后拿到继承连最底层构造函数的原型属性,然后顶层的构造函数使用这个原型属性构造一个对象, 经过中间一层一层的构造函数的加工返回出来。 所以对象是从父类返回的,在constructor中如果有继承需要先调用super才能拿到this 这个过程几乎描述了整个基于类构造对象的经过,但关于类实例化对象的过程,我还有以下疑问: 类有一个相对较新的语法如下: ``` class A { c = function () {} b = this.c.bind(this) } ``` 这个 = 是一个表达式用于创建实例的属性,是需要执行来计算属性值的,那么这个 b = this.c.bind(this)的执行环境是什么??? 合理的猜想是constructor 中super调用之后,因为这个环境中有this。 但是: ``` class A { constructor () { var e = 1 } c = e } ``` 这样实例化的时候会报错,如果 c = e 的环境在constructor中应该不会报错 除了在constructor中执行 c = e 找不到肉眼可见的作用域可以执行这个表达式了 ------------------------------------------------------------------------ 探索过程如下: class E { constructor () { console.log(222) // 2 } } class E1 extends E { constructor () { console.log(111) // 1 super() console.log(this.c) // 4 var e = 1 console.log(555) // 5 } c = (function() { console.log(333) // 3 return 'this.c' })() } c = …. 这个表达式是在super()之后调用的,但却不是在constructor中调用的, 感觉这其中隐藏了什么,望老师指点。 ------------------------------------------------------------------------ 感谢老师的时间 ------------------------------------------------------------------------ 还有一个非本专栏的问题想问下,为什么 = 是表达式 / 运算??? javascript语编3中描述 = 是运算,并不能理解 = 是运算(表达式)的说法(虽然确实如此,因为 它可以出现在表达式才能出现的地方,例如(a = 1)不会报错)。 一直以为 = 是语句,赋值语句,作用是将右边的值赋值给左边的引用,并不做运算,虽然有返回值, 但我理解这只是 = 的副作用。 当然符号是运算的说法也不能说服自己,例如 + - 是运算所以 = 符号也是运算这有点接受不了, 毕竟javascript语编3中还介绍了和return相对的yield还是表达式呢,所以没理由=不能是语句, = 不是语句的原因应该是它本身就不是语句和它是符号的形式没关系。 另:java中也可以打印出 a = 1的结果。 Number a = 0; System.out.println(a = 1); ------------------------------------------------------------------------ 再次感谢老师的时间 ------------------------------------------------------------------------

    作者回复: 关于类成员的一些新的规范,应该都是在讨论之中,目前尚没有定论,关于这个部分我在《JavaScript语言精髓与编程实践(第三版)》中有专门讨论过。但是因为目前没有写进规范的,所以我也不知道按哪种设计原则/原理来讲。(这涉及到对“类/对象声明”作用域的理解和设计,这尚未成规范) 关于第二个问题,“=”是有二义性的。在表达式的概念集合中,它确实是运算符;而在语句的概念集合中(例如“var x = 1”),它只是语法符号。对于后者,也就是语句来说,“var x”是声明,其中“x”称为“(被绑定的)标识符”;而“= 1”是作为单独一个语法组件来解释的,称为“初始绑定(初始器)”——而并不是“赋值”。 关于后面这一点,可以参考: https://tc39.es/ecma262/#prod-VariableDeclaration

    2020-09-19
    1
  • zy
    最近看到一段代码: { function foo(){} foo = 1 foo = 2 foo = 3 foo = 4 foo = 5 function foo(){} foo = 6 foo = 7 foo = 8 foo = 9 foo = 10 } console.log(foo) 打印出来是5,分析了半天没搞明白为什么,能不能请老师帮忙分析一下

    作者回复: 这段时间正好是在跟另一位同学聊相关的例子,于是写了一篇文章专门来讨论这个。在这里: https://blog.csdn.net/aimingoo/article/details/115270358 简单地说,就是块语句中的函数声明会存在两个隐式的提升操作,因此会导致全局中一个同名的变量得到块作用域中的、该函数名的当前值。

    2021-03-24
    2
  • HoSalt
    「ECMAScript 约定了优先取 Super 引用中的 thisValue 值,然后再取函数上下文中的」 「super.xxx() 是对 super.xxx 这个引用(SuperReference)作函数调用操作,调用中传入的 this 引用是在当前环境的上下文中查找的」 老师,前面一段话说可能从两个地方取「Super 引用中的 thisValue 值」是指通过bind方法绑定的值?

    作者回复: 第一句话是“从两个地方取”这个意思。——按顺序来确实如此。 但是由于Super引用中的thisValue总是存在,所以事实上只可能取到一个。 「Super 引用中的 thisValue 值」是特殊处理的,不是一般意义上(例如Function.prototype.bind)的绑定。

    2020-05-25
  • HoSalt
    「在 MyClass 的构造方法中访问 super 时,通过 HomeObject 找到的将是原型的父级对象。而这并不是父类构造器」 老师,理论上可以通过HomeObject.constructor 拿到 MyClass,是因为HomeObject.constructor拿到的值不靠谱,所以不这么去拿?

    作者回复: 的确是。constructor和constructor.prototype是外部原型链,不可依赖。

    2020-05-25
  • 穿秋裤的男孩
    ECMAScript 为此约定:只能在调用了父类构造方法之后,才能使用 super.xxx 的方式来引用父类的属性,或者调用父类的方法,也就是访问 SuperReference 之前必须先调用父类构造方法 ----------------- eg: class Parent { getName() { return this } }; class Son extends Parent { getName() { return super.getName() } }; const s = new Son(); s.getName(); // 会正常打印Son类。 ----------------- 疑问:现在的js引擎是不是会自动加一个默认的constructor函数,并且执行super(),不然按照老师的说法,这边在用super之前没有super(),是不能访问super.getName()的吧?

    作者回复: 是的。确实会“自动加一个”,这在下一讲里面有讲的。^^.

    2020-01-07
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部