题目引入
近来一向在看原型继续相干的东西,翻到这么一篇文章: 从ES6中的extends讲js原型链与继续
文中有一个点让我很感兴趣,箭头函数在继续过程当中没法经由过程super症结字猎取,这是为何呢?
前置学问
MDN上关于super的引见
The super keyword is used to access and call functions on an object’s parent – in MDN
大概有这么几个症结点:
- 子类中存在constructor要领的时刻,须要挪用super要领,而且须要在运用this症结字之前挪用
- super症结字能够用来挪用父对象上的要领
- 能够运用super来挪用父对象上的静态要领
- 不能够运用delete来删除super上的属性
- 不能够复写super对象上的只读属性
子类中是不是必需主动挪用super要领?
我的意见是不须要。
网上有些文章(比方这篇)写道:
由于若不实行super,则this没法初始化。
我的个人明白是,this是指代实行上下文环境的,不存在没法初始化的状况。更正确的说法是如许:假如不运用super要领,那末父类中的属性值没法举行初始化,假如这个时刻子类经由过程this字段来接见了父类中的属性值,那末只能获得一个undefined。至于为何这么写编译的时刻会报错?我的明白是,这应当是一种语法错误,而且是一种范例请求,ES6语法的范例请求,这类请求并不是说会影响到代码的现实实行。举个栗子:
// typescript中一段简朴的继续代码完成
class Parent {
name = 'parent';
func = function() {
console.log('func in parent called.');
}
}
class Child extends Parent {
age = 3;
func = function() {
console.log('age is: ', this.age); // 运用了this,不会报错
}
}
这段代码异常简朴,在子类中运用了this症结字,编译时不会报错,也能够一般实行。然后我们举行一点修改,在子类中引入constructor要领
class Child extends Parent {
age = 3;
// error TS2377: Constructors for derived classes must contain a 'super' call.
constructor() {
}
func = function() {
console.log('age is: ', this.age);
}
}
能够看到,编译阶段已最先报错了。在typescript的语法中,子类的constructor要领中不只须要挪用super要领,而且必需在第一行代码就挪用super,不然都是会报错的。看下面这段代码:
class Child extends Parent {
age = 3;
constructor() {
console.log('First line in constructor without super method');
super(); // error TS2376: A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties.
}
func = function() {
console.log('age is: ', this.age);
}
}
来,我们接着改
class Parent {
name = 'parent';
func = function() {
console.log('func in parent called.');
}
}
class Child extends Parent {
age = 3;
constructor() {
console.log('Show property of parent, name is: ', this.name); // error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
console.log('Show property of child, age is: ', this.age); // error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
super(); // error TS2376: A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties.
console.log('Show property of parent, name is: ', this.name);
console.log('Show property of child, age is: ', this.age);
}
func = function() {
console.log('age is: ', this.age);
}
}
能够看到,编译期已最先报种种错误了,不过这不主要,我们这里应用typescript的编译器(tsc)来举行编译,并检察编译后的代码内容:
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Parent = (function () {
function Parent() {
this.name = 'parent';
this.func = function () {
console.log('func in parent called.');
};
}
return Parent;
}());
var Child = (function (_super) {
__extends(Child, _super);
function Child() {
var _this = this;
_this.age = 3;
_this.func = function () {
console.log('age is: ', this.age);
};
console.log('Show property of parent, name is: ', _this.name); // 输出undefined,由于此时子类的实例上还没有继续到父类的属性值
console.log('Show property of child, age is: ', _this.age); // 输出3,子类实例自身的属性值能够接见
_this = _super.call(this) || this; // 组织函数式的继续完成,这一步就是讲父类的属性值设置到子类实例上
console.log('Show property of parent, name is: ', _this.name); // 输出parent,此时子类的实例上经由上一步的继续,获得了父类的属性值
console.log('Show property of child, age is: ', _this.age); // 输出3,子类实例自身的属性值能够接见
return _this;
}
return Child;
}(Parent));
//# sourceMappingURL=demo.js.map
由此能够晓得,在ES6中运用extends举行继续操纵的过程当中,
- 子类并不是必需挪用super要领,除非存在constructor要领
- 在constructor要领中应当起首挪用super要领,这是语法请求,不过这不是必需的
- 在挪用super要领之前,将没法经由过程this症结字来接见父类的属性(这里就能够诠释其他文章中提到的 ‘若不实行super,则this没法初始化’,更正确的说法应当是‘若不实行super,则没法将父类的属性值初始化到当前子类实例上’)
子类中运用super.prop和super[expr]的体式格局是怎样接见父类的属性和要领?
我们直接来看代码吧,症结点都解释了的
class Parent {
public name = 'parent';
public static staticName = 'staticParent';
public static staticFunc() {
console.log('staticFunc called in parent.');
}
public arrowFunc = () => {
console.log('arrowFunc called in parent.');
}
public normalFunc() {
console.log('normalFunc called in parent.')
}
}
class Child extends Parent {
public static staticFunc() {
super.staticFunc();
console.log('staticFunc called in Child.');
}
arrowFunc = () => {
super.arrowFunc();
console.log('arrowFunc called in Child.');
}
normalFunc() {
super.normalFunc();
console.log('normalFunc called in Child.')
}
getName() {
console.log('parent name is: ', super.name);
console.log('parent staticName is: ', super.staticName);
console.log('child name is: ', this.name);
}
}
/** 编译后的代码 **/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Parent = (function () {
function Parent() {
this.name = 'parent';
this.arrowFunc = function () {
console.log('arrowFunc called in parent.');
};
}
// 编译后的静态要领能够存在于Parent类的内部
Parent.staticFunc = function () {
console.log('staticFunc called in parent.');
};
Parent.prototype.normalFunc = function () {
console.log('normalFunc called in parent.');
};
return Parent;
}());
Parent.staticName = 'staticParent'; // 编译后的静态属性依旧存在于Parent类外
var Child = (function (_super) {
__extends(Child, _super);
function Child() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.arrowFunc = function () { // 子类实例挪用arrowFunc的时刻会报错,由于_super.prototype上是不存在arrowFunc要领的
_super.prototype.arrowFunc.call(_this); // Uncaught TypeError: Cannot read property 'call' of undefined
console.log('arrowFunc called in Child.');
};
return _this;
}
Child.staticFunc = function () {
_super.staticFunc.call(this); // super能够一般接见父类的静态要领
console.log('staticFunc called in Child.');
};
Child.prototype.normalFunc = function () {
_super.prototype.normalFunc.call(this);
console.log('normalFunc called in Child.');
};
Child.prototype.getName = function () {
console.log('parent name is: ', _super.prototype.name); // 输出undefined, 父类原型(_super.prototype)上不存在name属性
console.log('parent staticName is: ', _super.prototype.staticName); // 输出undefined,super没法一般接见父类的静态属性
console.log('child name is: ', this.name); // 输出parent,这是子类实例上的属性,继续自父类
};
return Child;
}(Parent));
//# sourceMappingURL=demo.js.map
这里再顺嘴提一句,关于静态属性和静态要领的区分。为何在子类中经由过程super症结字来猎取父类的静态要领经由编译后是_super.staticFunc,而猎取静态属性依旧是_super.prototype.staticName,从原型上猎取致使猎取失利呢?这个题目现在我还没有找到答案,愿望有晓得的小伙伴能够不吝珠玉。
不过我却是搜到一些其他相干内容。
Class 的静态属性和实例属性
由于 ES6 明确划定,Class 内部只要静态要领,没有静态属性。
虽然这类划定从ES7最先获得了修改,我们现在已能够将静态属性写在Class的内部,然则经由编译以后能够发明,静态属性依旧存在于类的完成的外部。
var Parent = (function () {
function Parent() {
this.name = 'parent';
this.arrowFunc = function () {
console.log('arrowFunc called in parent.');
};
}
// 编译后的静态要领能够存在于Parent类的内部
Parent.staticFunc = function () {
console.log('staticFunc called in parent.');
};
Parent.prototype.normalFunc = function () {
console.log('normalFunc called in parent.');
};
return Parent;
}());
Parent.staticName = 'staticParent'; // 编译后的静态属性依旧存在于Parent类外
回到题目自身
问:箭头函数在继续过程当中没法经由过程super症结字猎取,这是为何呢?
答:由于子类中运用super.prop和super[expr]的体式格局猎取的是父类原型(prototype)上的要领,静态要领除外。
参考资料
从ES6中的extends讲js原型链与继续
React ES6 class constructor super()
Class 的静态属性和实例属性