当前位置: 首页 > news >正文

ES6的Class继承--super关键字

概述

类的继承可以通过extends实现,让子类继承父类的属性和方法,而在子类内部(构造函数constructor)必须调用super()实现继承(super()代表父类构造函数,调用之后生成一个继承父类的this对象)

继承机制

ES6 的继承机制,与 ES5 完全不同:

  • ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”
  • ES6 的继承机制,则是先将父类的属性和方法,加到一个空对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”
  • 所以 ES6 的继承必须先调用super(),这样会生成一个继承父类的this对象,没有这一步就无法继承父类。这意味着新建子类实例时,父类的构造函数必定会先运行一次
  • super既可以作为函数使用,也可以作为对象使用,两种方式有很大的不同,详见下文

super作为函数

super()作为函数调用时(子内constructor内部必须调用),代表父类的构造函数,调用之后生成的是子类的实例,即super()内部this指向的是子类的实例(由构造函数this指向可得出,构造函数内部this指向的是生成的实例,而super()调用之后生成的是子类实例)

构造函数 this

【首先要明确this指向】:

  • 普通函数:内部的this指向函数运行时所在的对象,也即指向函数的直接调用者* 如果一个函数在全局环境运行,this就指向顶层对象(浏览器中为window对象)* 如果一个函数作为某个对象的方法运行,this就指向那个对象;* 如果一个函数作为构造函数,this指向它的实例对象
  • 箭头函数:没有自己的this对象,内部的this就是定义时上层作用域中的this

要注意的是,构造函数只能是普通函数,箭头函数不能作为构造函数(没有prototype),不可以对箭头函数使用new命令,否则会抛出一个错误

【其次要知道new操作符做了哪些事情】:

1.创建一个空对象 instance({})
2.将instance[[prototype]]属性指向构造函数fn的原型(即instance.[[prototype]] = fn.prototype),也即instance__proto__要指向构造函数的原型prototype
3.执行构造函数,使用 call/apply 改变 this 的指向 => 让this指向创建出来的实例instance
4.若函数返回值的是对象类型则作为new方法的返回值返回,否则返回刚才创建的全新对象

function isObject (target) {return (typeof target === 'object' || typeof target === 'function') && target !== null
}
function myNew (fn, ...args) {// 使用Object.create(Y),就说明实例的__proto__指向了Y,即实例的原型对象是Y// Object.create():创建一个新对象,使用现有的对象来提供新创建的对象的__proto__// 并且执行[[Prototype]]链接; 通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。const instance = Object.create(fn.prototype)// 改变this指向let res = fn.apply(instance, args)// 若res是个对象就直接返回,否则返回创建的对象// return res instanceof Object ? res : instancereturn this.isObject(res) ? res : instance
} 

如果不用new操作符直接调用,那么构造函数就相当于普通函数了,执行对象就 是window(严格模式下为undefined),即this指向了window(严格模式下为undefined

由此可以看出,构造函数内部this指向实例对象

super()的用法和注意点

【用法】

下面代码中,BA 的子类,B 继承 Asuper虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例

  • B里面的super()在这里相当于A.prototype.constructor.call(this)
 class A {constructor() {// `new.target`指向当前正在执行的函数console.log(new.target.name);}
}
class B extends A {constructor() {super();}
}
new A() // A
// 在`super()`执行时,它指向的是子类`B`的构造函数,而不是父类`A`的构造函数
// 即`super()`内部的`this`指向的是`B`的实例
new B() // B 

new.target指向被new调用的构造函数:在普通的函数调用中(函数调用),new.target的值是undefined在类的构造方法中,new.target指向直接被new执行的构造函数

【注意点】

  • 子类的构造函数constructor内必须执行一次super函数* 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象* 新建子类实例时,父类的构造函数必定会先运行一次
  • 在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错* super()调用之后才能让子类实例继承父类,然后才能在该基础上对子类实例进行属于它的特有操作
  • 不管有没有显式定义,任何一个子类都有constructor()方法,而且里面默认会调用super()* 作为构造函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错class A {}class B extends A {m() {super(); // 报错}} * 使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错class A {}class B extends A {constructor() {super();console.log(super); // 报错}} * 由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字```var obj = {toString() {return "MyObject: " + super.toString();}};obj.toString(); // MyObject: [object Object] ````super`作为对象

super作为对象时,在子类普通方法中,指向父类的原型对象;在子类静态方法中,指向父类

super在子类普通方法中作为对象

super作为对象在子类普通方法(非静态方法)中使用时,由于super指向父类的原型对象,所以定义在父类原型对象上的属性和方法都可以被访问到,而定义在父类实例上(父类构造函数内this.xxx)的方法或属性无法被访问到

  • 在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例,相当于执行的是 super.fn.call(this)

下例,父类A有定义在原型对象的属性b和方法p(),还有定义在实例对象的属性a;可以看到的是,定义在原型对象的bp都能被super.访问到,而定义在实例对象的a无法被访问到

/* ES6的class其实可以看成语法糖,新的`class`写法只是让对象原型的写法更加清晰、更像面向对象编程的语法
构造函数写法和下面的类写法是等价的
function A () {this.a = 123
}
A.prototype.p = function () {return 2
}*/
class A {constructor() {this.a = 123 // 定义在A的实例上的属性a}// 定义在A.prototype上的方法p() {return 2;}
}

A.prototype.b = 123 // 定义在A原型对象的属性b

class B extends A {constructor() {super();console.log(super.p()); // 2 => 相当于console.log(A.prototype.p())}getData() {console.log(super.a) // a定义在父类的实例对象上,所以为undefinedconsole.log(super.b) // b定义在父类的原型对象上,所以为123}
}

let b = new B();

b.getData()
// undefined
// 123 

在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例,相当于执行的是 super.fn.call(this)

class A {constructor() {this.x = 1; // 父类实例的x是1}print() {console.log(this.x);}
}

class B extends A {constructor() {super();this.x = 2; // 子类实例的x是2}m() {// 实际上执行的是`super.print.call(this)`super.print(); // 调用父类的方法,但是方法内部this是子类实例,所以输出2}
}

let b = new B();
b.m() // 2 

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性

这里表示不太能理解

class A {constructor() {this.x = 1;}
}

class B extends A {constructor() {super();this.x = 2;super.x = 3;console.log(super.x); // undefinedconsole.log(this.x); // 3}
}

let b = new B(); 

上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined

super在子类静态方法中作为对象

super作为对象在子类静态方法中使用时,这时super将指向父类,而不是父类的原型对象

  • 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
class Parent {static myMethod(msg) {console.log('static', msg);}myMethod(msg) {console.log('instance', msg);}
}

class Child extends Parent {static myMethod(msg) {// super作为对象在子类静态方法中使用,super指向的是父类// 也即要调用父类的myMethod方法,那就是父类的静态方法Parent.myMethod// 而属于一个类的方法,就是静态方法,也就是由static关键字定义的方法super.myMethod(msg); // 调用的是Parent.myMethod()}myMethod(msg) {// super作为对象在子类普通方法中使用,super指向的是父类的原型对象// 此时的super.myMethod()也即Parent.prototype.myMethod()// 由class语法糖和原来构造函数创建类的写法相对比可知super.myMethod(msg);}
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2 

总结

子类通过extends实现继承父类,子类constructor里必须调用super()来实现继承(super()在这里表示父类的构造函数):

  • 因为ES6继承机制就是调用super()生成一个继承父类的this然后把它作为子类的实例
  • 子类构造函数内只有在调用了super()之后才能使用this
  • 要注意的是,super()作为父类构造函数只能在子类constructor中使用
  • super()内部的this指向的是子类的实例,即可以把super()看成是父类.prototype.constructor.call(this)

super作为对象在子类普通方法(非静态方法)中使用时:

  • super指向的是父类的原型对象,所以定义在父类原型对象的属性和方法可以被super访问到,而定义在父类实例的属性和方法就不能被super访问到
  • super.fn()内部的this指向的是子类的实例,相当于执行super.fn.call(this);
  • 如果通过super对某个属性赋值,由于this指向子类实例,这时super就是this,赋值的属性会变成子类实例的属性

super作为对象在子类静态方法中使用时:

  • super指向的是父类,所以定义在父类的属性和方法可以被super访问到,由类的定义可知,定义在类上的属性和方法就是类的静态属性和静态方法,也就是类中用static定义的方法,可以直接用类去调用而不是通过实例调用
  • 而在子类静态方法中调用父类方法super.fn(),也即调用父类.fn()(这是一个父类的静态方法static fn()),内部this指向的是子类

对于上面在子类普通方法或静态方法中调用super.xxx()super.xxx()内部this指向,个人的理解是:super.xxx()更像是对父类方法的一个引用,而实际的this还要看当前所在子类方法的this,而this指向可以总结为_谁直接调用该方法,它内部的this就指向那个对象_ – 子类普通方法是由子类实例调用的,所以方法内部this指向子类实例,那么super.xxx()内部的this也就指向子类实例;而子类静态方法是由子类直接调用的(即子类.xxx()),所以方法内部this指向子类,那么super.xxx()内部的this也就指向子类了

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

相关文章:

  • Linux 简单命令 - find -grep命令深入学习
  • OP-TEE driver(五):libteec库中的接口在驱动中的实现
  • F-003 FPGA基础配置
  • java-php-python-ssm百分百教育集团教务管理系统设计计算机毕业设计
  • Apple M1 Macos 安装虚拟机软件UTM
  • 167.两数之和II-输入有序数组 || 双指针
  • aspnetcore6.0源代码编译调试
  • 【力扣刷题】Day04——链表专题
  • 云计算以及云计算安全相关的中文概述
  • 【 C++ 】哈希表底层结构剖析
  • Swift 基础语法 - 数据类型
  • js单行代码------对象
  • T1061 求整数的和与均值(信息学一本通C++)
  • Java注解-最通俗易懂的讲解
  • 特殊类设计
  • Bytom交易说明(账户管理模式)
  • CSS魔法堂:Absolute Positioning就这个样
  • Hibernate【inverse和cascade属性】知识要点
  • JavaScript服务器推送技术之 WebSocket
  • python docx文档转html页面
  • 百度小程序遇到的问题
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 回流、重绘及其优化
  • 马上搞懂 GeoJSON
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 排序算法学习笔记
  • 配置 PM2 实现代码自动发布
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 微信小程序实战练习(仿五洲到家微信版)
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (待修改)PyG安装步骤
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (三)elasticsearch 源码之启动流程分析
  • (五)网络优化与超参数选择--九五小庞
  • ***检测工具之RKHunter AIDE
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .NET BackgroundWorker
  • .Net Core 中间件验签
  • .net 托管代码与非托管代码
  • .Net6 Api Swagger配置
  • .net分布式压力测试工具(Beetle.DT)
  • .Net组件程序设计之线程、并发管理(一)
  • //解决validator验证插件多个name相同只验证第一的问题
  • @Bean, @Component, @Configuration简析
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell
  • [C#基础知识系列]专题十七:深入理解动态类型
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子
  • [GXYCTF2019]BabyUpload1 -- 题目分析与详解
  • [Hive] CTE 通用表达式 WITH关键字
  • [HNOI2008]水平可见直线