深入理解ES6中super关键字(super作为对象使用)

super关键字,有两种用法,一种是:super作为函数使用,具体介绍请进入深入理解ES5中super关键字(super作为函用数使),另外一种是:super作为对象使用。

    要注意:在使用super的时候,必须要显示的指定是作为函数使用,还是作为对象使用,否则会报错,因为,如果不能显示的指定,就无法准确的确定super到底要怎么使用。

本文将介绍super作为对象使用。

super作为对象使用时,要么对super进行类似get,要么是类似set功能,即要么赋值,要么取值,基本上就是这两种。我们通过对这两种分别举例,并通过babel转化后的代码进行分析这两种情况。

一、super类似取值时使用情况

当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[属性名]为undefined,super[方法名]此时会报一个错误,super[方法名]不是一个方法;
  • 如果查找到了,首先还要看一下当前属性是不是有get方法,如果有,则执行get方法,get方法内的this指向子类中的this(实例对象),如果没有get方法,要是super[属性名]的话,返回查找到的属性值,要是super[方法名],执行查找的方法,并且内方法部的this,是子类中的this(实例化)

当你使用super作为对象使用时,如果在子类静态方法中,你先去查找当前子类的父类的原型链(包括父类的原型及其以上原型链)上是否有这个属性或者方法,会有两种可能:

  • 如果没有查找到,则super[属性名]为undefined,super[方法名]此时会报一个错误,super[方法名]不是一个方法;
  • 如果查找到了,首先还要看一下当前属性是不是有get方法,如果有,则执行get方法,get方法内的this指向子类中的this(子类),如果没有get方法,要是super[属性名]的话,返回查找到的属性值,要是super[方法名],执行查找的方法,并且方法内部的this,是子类中的this(子类)

哈哈,现在呢,我们就去做几个例子来验证下。

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类似设置值时使用情况

我们可能会在方法中,使用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赋值的情况,请按照以下步骤处理:

  1. 如果super.x 是在普通方法中调用的话,先去父类的原型对象的原型链(父类的原型对象的原型链指的是:比如A代表父类,那这个链就是A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null,A.prototype、Object.prototype)上去查找这个属性,如果存在的话,如果他有set方法,那就去执行set方法(set方法里面的this指向的是子类的实例对象this),否则,如果它的writable为false,则结束,会抛出一个错误:failed to set property。要是前面两个条件都不满足,则去子类的实例对象(实例对象和实例方法)查找是否存在property属性,如果存在且wiritable为false,不做任何操作,返回failed to set property,其他情况就是去设置或者更改value
  2. 如果super.x 是在子类静态方法中调用的话,先去父类的原型链(父类的原型链指的是:比如A代表父类,那这个链就是A.__proto__ === Function.prototype  Function.prototype.__proto__ ===  Object.prototype Object.prototype.__proto__ === null,A、Function.prototype、Object.prototype)上去查找这个属性,如果存在的话,如果他有set方法,那就去执行set方法(set方法里面的this指向的是子类),否则,如果它的writable为false,则结束,会抛出一个错误:failed to set property。要是前面两个条件都不满足,则去子类静态属性上查找是否存在property属性,如果存在且wiritable为false,不做任何操作,返回failed to set property,其他情况就是去设置或者更改value。

其实上面可以写的更简洁点,只不过为了更好的理解罢了。

现在再去看刚开始的那个例子,是不是很简单,套这个不走就可以了,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 引擎确认,定义的是对象的方法

 

你可能感兴趣的:(ES6--类(class))