Notes on Software Development

Blackscript

Published: by Gabor de Mooij

I have been working with JavaScript since the nineties, although I have to admit I did not fully grasp the language until 2006, when I began working on an AJAX based CMS. Since then I have been trying to find my personal style of JavaScript, especially with regard to object-oriented programming.

In JavaScript there are many ways to leverage object-oriented programming techniques, you can use libraries to add syntactic sugar to make your programs look more object oriented, or you can go with one of the many patterns out there, like the module pattern or the prototype pattern. JavaScript is a very flexible language, maybe a bit too flexible. Ultimately, I settled with a very minimalistic approach towards JavaScript programming. I like to view my personal object-oriented programming style as a little language of its own, focusing on a tiny subset of JavaScript: BlackScript is how I like to call this little language of mine.

The name BlackScript echoes the minimalistic approach it encompasses. BlackScript is to JavaScript as black coffee is to java, without the milk and sugar, without the non-essential parts.

In this article I will share my favourite way to create object-oriented applications in JavaScript, the BlackScript way. As I said, BlackScript can be viewed as a separate language, a subset of JavaScript. All code written in BlackScript is valid JavaScript, but all JavaScript is not valid BlackScript. BlackScript can be defined as being JavaScript minus the following keywords:

  • new
  • this
  • prototype

Besides having no new operator, no prototypes and no 'this' keyword, BlackScript also lacks all related concepts like call/apply, __proto__ and the Object.create method. To maintain JavaScript compatibility, all those keywords and method names are simply considered reserved words.

Core ideas

The basic idea behind BlackScript is really simple; we only use literal objects, also known as objects created Ex Nihilo (from nothing). To create an object in BlackScript:

cat = {};

Methods are created by simply augmenting the object:

cat.meow = function() {
	alert('meow!');
}

Now this is ok, but I really like classes, so how do we create classes? A class in BlackScript is simply an object that plays the role of a class and offers a method to build an object for you. We denote classes with an uppercase character:

Cat = {
	createNew: function() {
		var obj = {};
		obj.meow = function() {
			alert('meow!');
		}
		return obj;
	}
}

Our class-like object is used like this:

cat = Cat.createNew();
cat.meow(); //displays alert box 'meow!'

Here, Cat is an object that acts like a class. It offers a method called 'createNew' that assembles a new instance for you. The name of the factory method 'createNew' is a convention I prefer. You could choose another name for the method if you like, but I think this name conveys the meaning of the method adequately: it refers to the new keyword to create a new object as well as the activity of creating a new object.
The createNew method creates another object ex nihilo and begins to add methods to it, here it adds the method meow to the cat instance. After adding the methods it returns the resulting object.

Private variables and methods

Thanks to the lexical scoping rules in JavaScript, you can easily implement variables that are, to a certain extent at least, private. Let's store the sound of the cat 'meow' in a private property:

Cat = {
	createNew: function() {
		var obj = {};
		var sound = 'meow';

		obj.meow = function() {
			alert(sound);
		}

		return obj;
	}
}

cat = Cat.createNew();
cat.meow();

As you can see, we can simply assign the string to a locally-scoped variable called sound and use it in our meow method. Private methods are implemented the same way, just store a function in a locally-scoped variable and use it within a publicly accessible method:

Cat = {
	createNew: function() {
		var obj = {};

		var makeSound = function() {
			alert('meow!');
		}

		obj.meow = function() {
			makeSound();
		}
		return obj;
	}
}

cat = Cat.createNew();
cat.meow();

The beauty of this approach is that it closely mimics traditional object-oriented programming, elegantly avoiding the oddities involving the use of the 'this' keyword while, at the same time refraining from using any syntactic sugar. No libraries or frameworks are required to make this code work. It even works with the oldest Javascript engines in the wild, something that cannot be said of the newest OOP techniques in JavaScript that require polyfills in older browsers.

Inheritance

Classical inheritance is also extremely easy to mimic. In BlackScript we mimic inheritance by creating a base object and then modify it to fit our needs: pragmatic and easy, yet effective. Let's explore how this works using an Animal and a Cat class, the Cat class extends the Animal class:

Animal = {
	createNew: function() {
		var obj = {};
		obj.makeSound = function( sound ) {
			alert( sound );
		}
		return obj;
	}
}

Cat = {
	createNew: function() {
		var obj = Animal.createNew();
		obj.meow = function() {
			obj.makeSound('meow!');
		}
		return obj;
	}
}

cat = Cat.createNew();
cat.meow();

As you can see, we're cheating here. There is no real inheritance but it's good enough. The Cat class simply manufactures an animal instance using the Animal class. The resulting object is equipped with a method called makeSound. The Cat builder then gratefully employs this method to implement the meow method.
Note that since the makeSound method is public in this example, we can make the cat in our example make other noises as well:

cat.makeSound('oink');

That's not what we want of course, so we have to make this a protected method, available to the subclass, but not to the outside world. How do we accomplish this?

Protected properties and methods

At first sight it seems rather hard to mimic protected properties and methods, however there is a very simple trick to pull this one off. First let me introduce the concept of a bundle. A bundle is an object shared between the parent and the child class, it's a kind of secret envelope, the contents of which is is hidden from the outside world but the parent and its child have access to it.
This bundle object forms the basis of our protected method implementation. The trick is to accept a bundle object in the parent class and add the protected methods to this object for usage by the child:

createNew: function( bundle ) {
		var protect = bundle;
		protect.makeSound = function() { ... }
}

This is an excerpt of the code we are going to use. This code snippet demonstrates the essence of our technique. This createNew method is part of the parent object. It takes the bundle and adds the method makeSound. In the Cat class-object, this bundle has local scope, so it isn't accessible to the outside world:

	var protect = {};
	Animal.createNew( protect );

To allow all class-like objects to have protected method, we need to add the bundle parameter to every createNew method. The resulting code looks like this:

Animal = {
	createNew: function(bundle) {
		var protect = bundle || {};
		var obj = {};

		protect.makeSound = function( sound ) {
			alert( sound );
		}
		return obj;
	}
}

Cat = {
	createNew: function(bundle) {
		var protect = bundle || {};
		var obj = Animal.createNew(protect);

		obj.meow = function() {
			protect.makeSound('meow!');
		}
		return obj;
	}
}

cat = Cat.createNew();
cat.meow();

Now, the makeSound method is accessible within the Cat object, but not from the outside. We cannot do:

cat.makeSound('oink');

Protected properties work exactly the same, only, instead of functions, we assign values to the 'protect' property.

Overriding methods

Let's say we have a LolCat class-object that extends the regular Cat. Besides producing the 'meow!' sound, this kind of cat utters a 'Whazup' sound. By doing so, it needs to override the meow method.
Like all the previous patterns, this feature of classical inheritance is extremely easy to mimic, all we have to do is simply overwrite the existing method and replace it with our own. However, we also want to call the overridden method. To facilitate this, we create a backup of our overridden method and store it in a local variable called _super (not the underscore, the word super is already a reserved word in JavaScript).

LolCat = {
	createNew: function(bundle) {

		var protect = bundle || {};
		var _super = {};
		var obj = Cat.createNew(protect);

		_super.meow = obj.meow;
		obj.meow = function() {
			_super.meow();
			protect.makeSound('Whazup?');
		}

		return obj;
	}
}

lolCat = LolCat.createNew();
lolCat.meow();

This lolCat instance will first produce the 'meow!' sound by invoking the overridden method in the parent class and then produce its own sound: 'Whazup?'
Once again the choice of the name '_super' is rather arbitrary, personally, I find it really clear but you could also name this property 'overridden' or 'original'.

Namespaces

Many object-oriented languages offer a feature called namespacing. Namespaces help you organize classes into packages.
With JavaScript and BlackScript this is easy to accomplish. Like with classes, we use literal objects that play the role of a package:

Application = {};
Application.Animals = {};
Application.Animals.Animal = {
	createNew: function() {
		....
	}
}

To reflect the inheritance pattern of our classes, we nest the Cat class-object in our Animal class-object:

Application.Animals.Animal.Cat

Same applies to the LolCat:

Application.Animals.Animal.Cat.LolCat

This basically means a Cat 'has' a LolCat object.

Static methods

Our createNew method can be viewed as a static method. If the Cat object plays the role of a class, and createNew plays the role of a constructor then, any method of the Cat object can be interpreted as a method of the Cat class.
This means, it is trivial to mimic static methods, we have already done so without realizing it! To clarify, here is another example. Let's add a static method to the Cat class to get the family of the species:

Cat = {
	getFamily: function() {
		return 'Felidae';
	}
}

This is all we have to do. Creating static methods is really simple.

Performance issues

I have now discussed all object-oriented features I use on a regular basis. I have shown you how all these classical object-oriented techniques can be used in JavaScript without touching the 'new' keyword, prototypes and the 'this' keyword. However, there is one problem. JavaScript is getting optimized for the latter. Our object-oriented approach also suffers from considerable overhead: each time you create an object, it has to be assembled by its creator, which has to assign all the functions to the resulting object. This means the BlackScript approach can become quite slow when used with a large number of objects. The remedy for this is to use proper abstractions. Let me give you an example: cats are known to have the ambition to take over the world. So they often organize some sort of conference to discuss the matter:

for (var i=0; i<100; i++) {
		var cat = Cat.createNew();
		if (cat.inFavourOfWorldDomination()) {
			vote++;
		}
}

Here we ask each cat to vote for a proposal to take over the world. Some cats will vote in favour, while others will not. The voting procedural itself performs awfully because each cat object needs to be assembled before it can vote.
To circumvent this issue, we simply need to add the concept of a 'group of cats', a collection object. Instead of a hundred objects, we'll just need one.

var cats = CatCollective.createNew(100);
var votes = cats.getVotesInFavourOfWorldDomination();

Once again, you might say we're cheating here, because what's inside the getVotes-method? The answer is, it does not really matter. You could allow the Cat collective object direct access to the array with votes, or you can devise another technique to bypass the individual cats. The point is that you can implement a fast method and still provide a beautiful way to interact with it.
This brings us to a very important conclusion about object-oriented programming in general. OOP is sometimes said to be slower than procedural or functional programming. While this might be true (or not, I don't care) - it simply does not matter. Object-oriented programming is all about a process of thought. Object-oriented techniques are about translating concepts from the outside world to code. The actual implementation, hiding behind the objects can be fast or slow. There is nothing within object-oriented programming or the BlackScript approach that makes your application inherently slow. Only if you have a very orthodox approach to OOP and you insist on asking every cat in the world individually whether it is in favour of world domination, you have a problem.

Return to my homepage.

Articles

A collection of articles I have written about software development.

Projects

 



a picture of meGabor de Mooij
Software Developer
Developing in PHP, Javascript and C.