在ES6版本之前,JavaScript语言并没有传统面向对象语言的class写法,ES6发布之后,babel迅速跟进,广大开发者也很快喜欢上ES6带来的新的编程体验。
当然,在这门“混乱”而又精妙的语言中,许多每天出现我们视野中的东西却常常被我们忽略。
对于ES6语法,考虑到浏览器的兼容性问题,我们还是要把代码转换为ES5版本运行。然而,之前的ES版本为什么能模仿ES6的诸多特性,比如class与继承,super,static?JavaScript又做了哪些改变以应对这些新角色?本文将对class实例构造,class继承关系,super关键字,static关键字的运行机制进行探索。
水平有限,文中若有引起困惑或错误之处,还望指出。
基本而言,ES6 class形式如下:
class Whatever{
}
当然,还可以有constructor方法。
class Whatever{
constructor(){
this.name = 'hahaha';
}
}
请看ES5对应版本:
function Whatever{
this.name = 'hahaha';
}
可知,constructor相当于以前在构造函数里的行为。而对于ES5构造函数而言,在被new调用的时候,大体上进行了下面四步:
所以,构造函数的实例会继承挂载在prototype上的方法,在ES6 calss中,我们这样写会把方法挂载在class的prototype:
class Whatever{
//...
methodA(){
//...
}
}
对应ES5写法:
Whatever.prototype = function methodA(){
//...
}
在基于原型的语言,有以下四个特点:
看到这,大家应该明白了,为什么挂载在Constructor.prototype的方法会被实例“继承”!
在ES6 class中,继承关系还是由[[prototype]]连维持,即:
Child.prototype.__proto__ === Parent.prototype;
Child.__proto__ === Parent;
childObject.__proto === Child.prototype;
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);
});
};
当我们谈论继承时,往往指两种:
上文中我们探讨了第一种,现在,请把注意力转向第二种。
考虑下方代码:
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可以让我们在子类中借用父类的属性和方法。
methodA(){
super.methodA();
console.log('methodA in Child')
}
super关键词真是一个增进父子情的天才创意!
值得注意的是,子类中methodA调用super.methodA()时候,super.methodA中的this绑定到了子类实例。
用的舒服之后,我们有必要想一想,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,只会鬼打墙。
为了应对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不允许在对象的非方法中调用
}
}
结合前文中关于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关键词标志了一个挂载在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
因为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
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
ES6中继承的子类中,如果使用构造函数constructor()那么就必须使用 super()方法初始化,这样下面才可以调用this关键字。super()只能用在子类的构造函数之中,用在其他地方就会报错。
我听说 Hooks 最近很火。讽刺的是,我想用一些关于 class 组件的有趣故事来开始这篇文章。你觉得如何?本文中这些坑对于你正常使用 React 并不是很重要。 但是假如你想更深入的了解它的运作方式,就会发现实际上它们很有趣。
super()相当于Parent.prototype.constructor.call(this)ES5的继承,实质上是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.call(this)). ES6的继承
据说 Hooks 势头正盛,不过我还是想略带调侃地从 class 的有趣之处开始这篇博客。可还行?这些梗对于使用 React 输出产品并不重要
Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承,super关键字既可以当做函数使用,也可以当做对象使用,当做函数使用的时候,代表的是父类的构造函数
JavaScript 语言在ES6中引入了 class 这一个关键字,在学习面试的中,经常会遇到面试官问到谈一下你对 ES6 中class的认识,同时我们的代码中如何去使用这个关键字,使用这个关键字需要注意什么,这篇来总结一下相关知识点。
this 和 super 都是 Java 中的关键字,都起指代作用,当显示使用它们时,都需要将它们放在方法的首行(否则编译器会报错)。this 表示当前对象,super 用来指代父类对象
记录下class中的原型,实例,super之间的关系,构造器中的this指向实例对象,在构造函数上定义的属性和方法相当于定义在类实例上的,而不是原型对象上
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!