super关键字,有两种用法,一种是:super作为函数使用,具体介绍请进入深入理解ES5中super关键字(super作为函用数使),另外一种是:super作为对象使用。
要注意:在使用super的时候,必须要显示的指定是作为函数使用,还是作为对象使用,否则会报错,因为,如果不能显示的指定,就无法准确的确定super到底要怎么使用。
本文将介绍super作为对象使用。
super作为对象使用时,要么对super进行类似get,要么是类似set功能,即要么赋值,要么取值,基本上就是这两种。我们通过对这两种分别举例,并通过babel转化后的代码进行分析这两种情况。
当super类似取值时,比如说super.x、super.toString()等等,一般都是先去查找父类原型对象上有没有这个方法或者属性,如果没有,再去往更高级继承类的原型对象上去查找。接着我们举个实例分析:
实例:
class A {
constructor() {
this.x = 'A';
}
getX() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 'B';
console.log(super.x);
}
getX() {
super.getX();
}
static getY() {
super.getY();
}
}
babel转化后:
var A = /*#__PURE__*/function () {
function A() {
_classCallCheck(this, A);
this.x = 'A';
}
_createClass(A, [{
key: "getX",
value: function getX() {
console.log(this.x);
}
}]);
return A;
}();
var B = /*#__PURE__*/function (_A) {
_inherits(B, _A);
var _super = _createSuper(B);
function B() {
var _this;
_classCallCheck(this, B);
_this = _super.call(this);
_this.x = 'B';
console.log(_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this)));
return _this;
}
_createClass(B, [{
key: "getX",
value: function getX() {
_get(_getPrototypeOf(B.prototype), "getX", this).call(this);
}
}], [{
key: "getY",
value: function getY() {
_get(_getPrototypeOf(B), "getY", this).call(this);
}
}]);
return B;
}(A);
通过对babel转化后的代码分析,明显看出super.x、super.getX(),静态方法中的super.getY()转化成_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this))、_get(_getPrototypeOf(B.prototype), "getX", this).call(this), _get(_getPrototypeOf(B), "getY", this).call(this),都涉及到_get方法,现在呢?我们就分析_get(),_get()代码如;
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}
分析三个super的使用,_get方法中,第一个参数_getPrototypeOf(target)指的是什么呢?对于target,如果你在当前子类的普通方法中使用super,那target指的是当前子类的prototype(即B.prototype),如果你在子类静态方法中使用super,那target指的是当前子类(即B),第二个参数指的是你通过super调用的方法名或者属性名(即x, getX, getY),第三个参数是当前this(如果是在子类普通方法中,this指向实例对象,如果在静态方法中,this指向子类),如果你是在constructor中使用super作为对象的话,第三个参数会为_assertThisInitialized(_this),为啥在子类constructor中要写成_assertThisInitialized(_this)呢,因为要判断super(super作为对象时使用)使用是否是在super()之后,因为super()之后,才有子类的this对象(如果想了解这个,请进入深入理解ES5中super关键字(super作为函用数使))。
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
_getPrototypeOf(target)返回的是target的原型__proto__,比如在当前子类的普通方法中调用super,_getPrototypeOf(target)返回的是子类的prototype的__proto__(即B.prototype.__proto__),如果在子类的静态方法中调用super,它返回的是子类的__proto__(即B.__proto__)。
_superPropBase(target, property)作用是检查target的原型链上是不是有property属性或者方法(上面实例中,检查A.prototype、A的原型链上是否有property属性或者方法),如果没有检查到,则_get()返回undefined,如果检查到了,就执行Object.getOwnPropertyDescriptor(base, property)来获取property的描述对象,如果property描述对象上有get方法,就执行get方法,将get方法中的this指向receiver,如果没有就返回desc.value,desc.value可以是一个属性,也可以是一个方法。
所以总结如下:
当你使用super作为对象使用时,如果在普通方法中,你先去查找当前子类的父类的prototype的原型链(包括父类上的原型对象及其以上原型链)上是否有这个属性或者方法,会有两种可能:
当你使用super作为对象使用时,如果在子类静态方法中,你先去查找当前子类的父类的原型链(包括父类的原型及其以上原型链)上是否有这个属性或者方法,会有两种可能:
哈哈,现在呢,我们就去做几个例子来验证下。
class A {
constructor() {
this.x = 'A';
}
getX() {
console.log(this.x);
}
static getY() {
console.log(this.x);
}
get t() {
return '普通方法 get t()';
}
set t(t) {
console.log('普通方法 set t() ', t);
}
static get t() {
return '静态 get t()';
}
static set t(t) {
console.log('静态方法 set t() ', t);
}
}
A.prototype.x = '父类A原型上定义的属性';
A.x = '父类A上定义的静态属性';
class B extends A {
constructor() {
super();
this.x = 'B';
console.log(super.x); //第一个super运用
console.log(super.y); //第二个super运用
}
getX() {
super.getX(); //第三个super运用
}
getZ() {
super.getZ(); //第四个super运用
}
static getY() {
super.getY(); //第五个super运用
console.log(super.x); //第六个super运用
console.log(super.y); //第七个super运用
}
static getW() {
super.getW(); //第八个super运用
}
getT() {
console.log(super.t); //第九个super运用
}
static getT() {
console.log(super.t); //第10个super运用
}
}
B.x = '子类B上的静态属性';
实例代码如上所示,结果如下:
1、当进行let b = new B();时,会执行第一个、第二个console.log,根据我们上面的分析,这两个super.x、super.y是在普通方法中,因此,我们需要去检查A.prototype的原型链上是否有这两个x、y,分析A.prototype原型链(如果想了解原型链,请进入JavaScript--原型链):
A.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
检查A.prototype的整个原型链发现,只有A.prototype.x,没有找到y,同时对于x,y,又没有get方法,因此,console.log(super.x)打印为:"父类A原型上定义的属性",而console.log(super.y)打印undefined。
2、当进行b.getX()时,执行super.getX(),因为b.getX是一个普通方法,那么我们还是要去检查A.prototype的原型链上去查看是否有getX,由上面原型链的分析,可得在父类的原型中有一个getX,因此super.getX执行的是A.prototype.getX,但是要注意的是,A.prototype.getX方法里面的this对象是子类中实例对象(this),因此A.prototype.getX中的console.log(this.x)打印的是子类B的实例对象上实例属性x,即"B"。
3、当执行b.getZ()时,执行super.getZ(),发现整个A.prototype原型链上没有getZ,因此此处会报一个错误提示,提示(intermediate value).getZ is not a function。
4、执行B.getY()时,getY时子类B的静态方法,那么内部代码中super.getY()、super.x、super.y,这些属性和方法,我们需要去父类A的原型链上去查找,父类A的原型链(如果想了解原型链,请进入JavaScript--原型链)如下:
A.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
super.getY其实执行的应该是父类A的静态方法getY,同时要注意,A.getY方法中的this指向的是子类B,而不是子类B的实例对象,因此执行B.getY(),依次打印出:"子类B上的静态方法" “父类A上定义的静态属性” undefined。
5、执行B.getW()时,由于在父类A上的原型链上不存在getW,因此此处会报一个错误,提示(intermediate value).getW is not a function。
6、执行b.getT()时,super.t返回什么呢?首先,super.t是处于普通方法中,所以我们应该去检查父类的原型的原型链上是否有t,经检查,父类的原型的原型链上有t属性,之后,t属性的描述对象上有get方法,因此,将会执行get方法,所以打印出:"普通方法 get t()" 。
7、执行B.getT()时,super.t返回什么呢?首先,super.t是处于子类的静态方法中,所以我们应该去检查父类的原型链上是否有t,经检查,父类的原型链上有t属性,之后,t属性的描述对象上有get方法,因此,将会执行get方法,所以依次打印出:"静态 get t()" 。
我们可能会在方法中,使用super.x = 11等类似设置值的情况,这种情况内部是如何操作的呢?
下面是来自阮一峰关于ES6中的一个实例,通过分析这个,来详细说明内部操作。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
babel转化后:
var A = function A() {
_classCallCheck(this, A);
this.x = 1;
};
var B = /*#__PURE__*/function (_A) {
_inherits(B, _A);
var _super = _createSuper(B);
function B() {
var _this;
_classCallCheck(this, B);
_this = _super.call(this);
_this.x = 2;
_set(_getPrototypeOf(B.prototype), "x", 3, _assertThisInitialized(_this), true);
console.log(_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this))); // undefined
console.log(_this.x); // 3
return _this;
}
return B;
}(A);
super.x = 3,转化后为_set(_getPrototypeOf(B.prototype), "x", 3, _assertThisInitialized(_this), true),通过上面_get的分析,很容易理解这个,_assertThisInitialized(_this)是判断子类constructor中在使用super.x = 3之前是否已经super()·。_getPrototypeOf(B.prototype)目的是查找B.prototype.__proto(其实是它的父类的原型对象)。下面分析下_set方法:
function _set(target, property, value, receiver, isStrict) {
var s = set(target, property, value, receiver || target);
if (!s && isStrict) {
throw new Error('failed to set property');
}
return value;
}
function set(target, property, value, receiver) {
if (typeof Reflect !== "undefined" && Reflect.set) {
set = Reflect.set;
} else {
set = function set(target, property, value, receiver) {
var base = _superPropBase(target, property);
var desc;
if (base) {
desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.set) {
desc.set.call(receiver, value);
return true;
} else if (!desc.writable) {
return false;
}
}
desc = Object.getOwnPropertyDescriptor(receiver, property);
if (desc) {
if (!desc.writable) {
return false;
}
desc.value = value;
Object.defineProperty(receiver, property, desc);
} else {
_defineProperty(receiver, property, value);
}
return true;
};
}
return set(target, property, value, receiver);
}
代码中,主要还是var s = set(target, property, value, receiver || target); target表示父类的原型对象(A.prototype),property表示要设置的属性(比如super.x = 3,property表示'x'),value表示属性的设置值,receiver表示当前this(如果在普通方法中,表示实例对象,如果在静态方法中,receiver表示当前类),set方法内部实现:
1、var base = _superPropBase(target, property); 表示在target的原型链上是否存在property属性,并返回存在property属性的原型对象,否则返回null,比如上面的实例中,原型链是:A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null,如果想了解原型链,请进入JavaScript--原型链
2、如果找到了存在property属性的原型对象,则返回property的描述对象,如果property存在存值函数set,就执行set方法,并将set方法中的this指向实例对象或者子类(如果super在普通方法中使用,this指向子类的实例对象,如果在静态方法中使用,this指向子类),如果没有set方法,就去查看property是否可以修改,如果不能修改,就结束,返回false,会抛出一个错误:failed to set property
3、如果在target的原型链上,存在的property,即没有set方法,也没有设置writable为false,则就去执行let desc = Object.getOwnPropertyDescriptor(receiver, property),返回receiver(如果super在普通方法中使用,receiver指向子类的实例对象,如果在静态方法中使用,receiver指向子类)上的property的描述对象,如果desc不为null,如果desc的writable为false,不做任何操作,否则,将value赋值给desc.value,将本身的property配置原封不动设置。
4、如果desc为null,则receiver上设置property属性值为value,并且property的配置设置都为true(enumerable: true, configurable: true, writable: true)。
总结如下:
如果你在子类中碰到类似super.x = 123赋值的情况,请按照以下步骤处理:
其实上面可以写的更简洁点,只不过为了更好的理解罢了。
现在再去看刚开始的那个例子,是不是很简单,套这个不走就可以了,super.x = 3处于子类的constructor(普通方法)中,在父类的原型对象的原型链(父类的原型对象的原型链指的是:比如A代表父类,那这个链就是A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null,A.prototype、Object.prototype)上没有x这个属性,则去子类的实例对象(实例属性和实例方法,不要和子类的原型对象上的属性和方法弄混)查找这个x属性,这个子类实例属性x的writable又不为false,所有说super.x = 3,其实质是this.x = 3(this指的是实例对象),所有console.log(this.x)打印的是3。
下面呢,我们再举个例子
class A {
constructor() {
this.x = 1;
}
set t(t) {
console.log('父类A的x属性的set方法 ', t);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
this.t = 2;
super.t = 3;
console.log(super.x); // undefined
console.log(this.x); // 2
}
}
let b = new B();
代码上图所示,执行之后,打印出:'父类A的x属性的set方法 3' undefined 2
注:由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super
关键字
注:super
关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法