Wednesday 8 February 2017

ES6 and Super

Exploring ES6 is an amazing book. I've been going through some of its chapters randomly and at different levels of depth. It lately caught my attention that in ES6 the syntax for defining methods in one class can be also used in object literals. This means that you can write this:

let user = {
	sayHi: function(st){
	......
	}
};

or this:

let user = {
	sayHi(st){
	......
	}
};

They are almost the same, but not exactly. The first point is to realize that in ES6 we have different kinds of callable entities, though all of them are functions. Reading this chapter these callables are instances of function, but on creation (depending on whether it's a method, an arrow function...) they will get different internal data and methods ([[Construct]], [[HomeObject]]... We could say that each particular callable that we create in our code (via function, arrow, method definition) are instances of different function types.

For method declarations, we can read in this other chapter:

You can’t move a method that uses super: Such a method has the internal slot [[HomeObject]] that ties it to the object it was created in. If you move it via an assignment, it will continue to refer to the superproperties of the original object. In future ECMAScript versions, there may be a way to transfer such a method, too.

I've done a small test to verify that I had got it right:



let stringManagerABC = {
		process(st){
		st = st + " [ProcessingABC applied]";
		return st;		
	}
};

//a normal case, we can say that stringManagerDEF -> stringManagerABC 
let stringManagerDEF = {
	process(st){
		st = super.process(st) + " [ProcessingDEF applied]";
		return st;		
	}
};

Object.setPrototypeOf(stringManagerDEF, stringManagerABC); 
//
console.log(stringManagerDEF.validate("text"));

console.log("------------------------------");

let stringManager123 = {
	process(st){
		st = st + " [Processing123 applied]";
		return st;		
	}
};

//a rather particular case, stringManagerMixed -> stringManager123, but for the process method we are directly giving it the one from stringManagerDEF 
let stringManagerMixed = {
};


Object.setPrototypeOf(stringManagerMixed, stringManager123); 

stringManagerMixed.process = stringManagerDEF.process;

console.log(stringManagerMixed.process("text"));
//text [ProcessingABC applied] [ProcessingDEF applied]
//Not good, it's calling the method in the original parent rather than in the real parent

My understanding from some additional reading is that the [[HomeObject]] field of one method points to the object where the function has been attached at creation time (MyClass.prototype when defined in a class or the "this" at the definition time when in an object literal), so it's static. Finding a generic way to obtain [[HomeObject]] in a dynamic way does not seem simple. One could think of using "this", hasOwnProperty and getPrototypeOf, but that would only work if the inheritance chain has only 2 levels. With more levels we would be going down again in the prototype chain. Of course we could forget about this [[HomeObject]] thing and directly do the super calls by means of MyParentClass.prototype.method. That is not good either, for this same case of moving a method to another object it does not apply, and it would fail in cases like the mixins that we saw last week (you are dynamically creating subclasses).
For the especific case of the sample above, we could use something like the below, but it does not extend to other situations:



let stringManagerABC = {
	validate: function(st){
		st = st + " [ValidationABC applied]";
		return st;		
	}
};

//a normal case, we can say that stringManagerDEF -> stringManagerABC 
let stringManagerDEF = {
	validate: function(st){
		let _proto = Object.getPrototypeOf(this);
		if (_proto.validate){
			st = _proto.validate(st);
		}
		//I can not use super in a method declared via function
		//st = super.validate(st) + " [ValidationDEF applied]";
		return st + " [ValidationDEF applied]";		
	}
};

Object.setPrototypeOf(stringManagerDEF, stringManagerABC); 
console.log(stringManagerDEF.validate("text"));

console.log("------------------------------");

let stringManager123 = {
	validate: function(st){
		st = st + " [Validation123 applied]";
		return st;		
	}
};

//a rather particular case, stringManagerMixed -> stringManager123, but for the process method we are directly giving it the one from stringManagerDEF 
let stringManagerMixed = {
};


Object.setPrototypeOf(stringManagerMixed, stringManager123); 

stringManagerMixed.validate = stringManagerDEF.validate;

console.log(stringManagerMixed.validate("text"));
//text [Validation123 applied] [ValidationDEF applied]
//so this is good, I'm calling to the method in my real parent

No comments:

Post a Comment