Friday 25 January 2013

Lazy Object Construction

It's funny to see how an initial interesting reading (about the present and future of JavaScript's arguments object) takes me to a pretty good answer in StackOverflow respecting how to invoke a constructor using apply, and that ends up leading me to an old post of mine and rethinking it in JavaScript.

The idea behind Lazy objects is simple. We have an object which construction/initialization can be rather costly, so we create an empty facade to that object that we can pass around, and then when the client really needs the the real object, this facade will take care of its creation.

Drawing inspiration from the Lazy class in .Net, and from the aforementioned article, I ended up with 2 different approaches to the problem

Approach 1

function Lazy(type){
	var _arguments = [].slice.call(arguments, 1);
	this.create = function(){
		var inst = Object.create(type.prototype);
		//need this extra inst2 to follow the correct behaviour in case the constructor returns somethins
		var inst2 = type.apply(inst, _arguments);
		return inst2 || inst;
	};
}

var p = new Lazy(Person, "Xuan", 37);
p = p instanceof Lazy ? p.create() : p;
console.log(p.sayHi());

I would say this first approach seems cleaner to me based on the usage pattern. Consumers have to be aware of being dealing with a Lazy wrapper, but once they obtain the real object, they work directly with it

Approach 2

function Lazy2(type){
	var _arguments = [].slice.call(arguments, 1);
	Object.defineProperty(this, "val",{
		get: function(){
			//replace the property on first access (lazy access...)
			Object.defineProperty(this, "val", {
				value: (function(){
					var inst1 = Object.create(type.prototype);
					//need this extra inst2 to follow the correct behaviour in case the constructor returns something
					var inst2 = type.apply(inst1, _arguments);
					return inst2 || inst1;
				}()),
				writable: false
			});
			return this.val;
		},
		enumerable: true,
		configurable: true
	});
}

var p2 = new Lazy2(Person, "Xuan", 37);
if (p2 instanceof Lazy2){
	console.log(p2.val.sayHi());
}
else
	console.log(p2.sayHi());

This other approach, though more interesting in its implementation (for the val property I'm using an accessor descriptor that on first access replaces itself with a data descriptor, yes, pure JavaScript magic...) seems less appealing to me cause we're forcing the client to always access the functionality in the object through "val". Well, it's the same that happens with .Net's Lazy.

In both cases our Lazy function receives a constructor function as parameter, and we use the same technique to properly invoke it:

//create an object with its __proto__ pointing to type.prototype
var inst1 = Object.create(type.prototype);

//pass that object as "this" to the constructor, along with the other arguments
var inst2 = type.apply(inst1, _arguments);

//let's be consistent with how the new operator works, if the constructor returns a value let's return it instead of our initial object
return inst2 || inst1;

Notice that I'm using a Lazy constructor function instead of a lazy factory function. It seems more natural to me, but I've also included here the factory function approach.

A better solution would be using proxies, something like what I implemented for .Net in that previous entry. I'm not familiar at all with ES6 proxies, so maybe I'll give it a go in the future (but with ES6 still far in the horizon, I prefer to focus on other tasks). I've already complained previously about JavaScript not exhibiting any sort of Invoke Method or Get Property hooks. If that were the case, implementing the proxy technique would be so drastically simplified. The clear advantage of proxies is that the "laziness" of the object turns invisible to users, who interact with it right the same as with a non lazy version. This is ideal, as being "Lazy" is an implementation detail that should not get leaked to the client.

No comments:

Post a Comment