Hedger Wang
HTML/CSS/JS Fanboy.
Hedger Wang used to work on Yahoo! as Frontend Tech Lead and now he works for Google and continue working on several big projects as UI Developer.
Intro
While many programmers do think that Object Oriented JavaScript is a good approach to take, it’s also known that it’s hard to write robust OO-style JavaScript simply due the nature of the language itself and the environment which it’s running in (mostly are browsers).
Using Google Closure Compiler can not only help you to compress the the code, but it also compiles it just like any compiler does.
When the compiler’s flag ADVANCED_OPTIMIZATIONS is enabled, you can get a lot more optimization than most JavaScript compressors such as YUI Compressor, Dojo Compressor.
This article will talk about several common OO-style patterns and how they are implemented now or how they should be implemented.
In the end, you shall see how you can write formal OO-Style code and keep the performance boosted with the help of Closure Compiler.
Have fun!
OO-style JavaScript is considered expensive
For a long time, for JavaScript, due to its nature and its host environment, the browser, it’s usually considered hard to write pure Object-Oriented javascript application. Nicholas Zakas, one of the most well-known Black Belt Karate JavaScript programmers, once wrote a BLOG post about the pain of writing OO-style JS code.
In traditional object-oriented languages, classes and inheritance come for free. There’s no performance penalty for increasing the number of classes or having a deep inheritance tree – the compilers know their job good enough to save the details from you. In JavaScript, reference types do come with a penalty, and inheritance comes with an additional penalty.
JavaScript simply has no Class
Though OO-style JS has its drawback such as runtime performance penalty, people still tries varies of different way to achieve such a goal of using more OO-style JS to replace functional JS.
For example, John Resig has an interesting way to ensure that a “Class” function is always used as a constructor no matter the whether new keyword is used.
// John Resig's Approach. function User(name){ if ( !(this instanceof User) ) return new User(name); this.name = name; } var userA = new User('John'); // An instance. var userB = User('Jane'); // Also an instance.
JavaScript has no access control
Moreover, Douglas Crockford also proposed a module pattern that demonstrates how to protect private members from being read or written by external Object.
// Crockford's approach. function User(name) { var _name = name; // Getter for the private name. this.getName = function() { return _name; }; }
Global variables are bad, but deeply name-spaced variables aren’t anything better, too.
While global variables are considered evil, using deeply name-spaced variables are also considered slow.
// Global is evil. var myProjectDemoUserName = 'foo'; // Meeh... my.project.demo.user.Name = 'foo';
Developers can’t afford redundancy
How about loading the entire YUI DOM library to the document when all you really need is just one static method YAHOO.util.Dom.getStyle(document.body, ‘backgroundColor’) to get your job done?
Simplicity isn’t that simple
Or, you plug in your jQuery library then quickly realize that there’s nothing built in to handle the DOM Range and Selection Model. Also there’s no Data Collection, no Component framework, no Class Inheritance or anything that you might need to build an complex web application.
Only JavaScript Ninjas survive?
So this is why everyone is trying so hard to hire a JavaScript ninja or become one
However, is it really necessary to write JavaScript like this?
if (!("a" in window)) { var a = 1; } alert(a);
In fact,Code like this should never be written, it’s the bad part of JavaScript. No C++, Java programmer would ever or be allowed to write a code like this.Programmer should focus on algorithm and data structure, not this kind of confusing tricks.
Can we have all the Good Parts?
It appears it’s almost unlikely to write JavaScript that is light-weight, generic, robust and can be used in many places while being maintainable, capable of providing rich set of functionalities.
Does it sound really sad to you? Would this make JavaScript nothing more than a toy language?
No worries, Closure Compiler comes to the rescue.
Let Closure Compiler make you a JavaScript Samurai!
The Closure Compiler is a tool for making JavaScript download and run faster. It is a true compiler for JavaScript. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what’s left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.
Since the official web site already has a lot of great resources about how to use the compiler. Instead of writing a article about how to use the compiler, which actually requires writing a chapter of a book, let me focus on solving the issues that I mentioned earlier.
User @constructor to annotate a function as a Class.
Instead of doing run-time check inside the constructor, you should let the compiler do the job for you. Note you should avoid using run-time check whenever this job can be done by the compiler, which is the key idea that you should remember.
/** * @constructor */ function MyClass() { } // Pass. var obj1 = new MyClass(); // ERROR: Constructor function (this:MyClass): // class should be called with the "new" keyword. var obj2 = MyClass(); // Error.
Use @private for access control.
// File demo1.js // /** * A User. * @constructor */ function User() { /** * The creation date. * @private * @type {Date} */ this._birthDay = new Date(); } /** * @return {number} The creation year. */ User.prototype.getBirthYear = function() { return this._birthDay.getYear(); }; // File demo2.js // // Create a user. var me = new User(); // Print out its birth year. document.write(me.getBirthYear().toString());
The compiler will ensure that the private member _birthDay isn’t read or write through out the whole application. Only code in the same JS can access objects that are marked @private. Similarly, you can also use @protected to annotate your code.
Use @extend to manage class inheritance.
Say that we have three classes Shape, Box and Cube.
Class Shape defines a common method getSize()
Class Box extends Shape.
Class Cube extends Box
/** * Helper function that implements (pseudo)Classical inheritance inheritance. * @see http://www.yuiblog.com/blog/2010/01/06/inheritance-patterns-in-yui-3/ * @param {Function} childClass * @param {Function} parentClass */ function inherits(childClass, parentClass) { /** @constructor */ var tempClass = function() { }; tempClass.prototype = parentClass.prototype; childClass.prototype = new tempClass(); childClass.prototype.constructor = childClass; } ////////////////////////////////////////////////////////////////////////////// /** * The shape * @constructor */ function Shape() { // No implementation. } /** * Get the size * @return {number} The size. */ Shape.prototype.getSize = function() { // No implementation. }; ////////////////////////////////////////////////////////////////////////////// /** * The Box. * @param {number} width The width. * @param {number} height The height. * @constructor * @extends {Shape} */ function Box(width, height) { Shape.call(this); /** * @private * @type {number} */ this.width_ = width; /** * @private * @type {number} */ this.height_ = height; } inherits(Box, Shape); /** * @return {number} The width. */ Box.prototype.getWidth = function() { return this.width_; }; /** * @return {number} The height. */ Box.prototype.getHeight = function() { return this.height_; }; /** @inheritDoc */ Box.prototype.getSize = function() { return this.height_ * this.width_; }; //////////////////////////////////////////////////////////////////////////// /** * The Box. * @param {number} width The width. * @param {number} height The height. * @param {number} depth The depth. * @constructor * @extends {Box} */ function Cube(width, height, depth) { Box.call(this, width, height); /** * @private * @type {number} */ this.depth_ = depth; } inherits(Cube, Box); /** * @return {number} The width. */ Cube.prototype.getDepth = function() { return this.depth_; }; /** @inheritDoc */ Cube.prototype.getSize = function() { return this.depth_ * this.getHeight() * this.getWidth(); }; //////////////////////////////////////////////////////////////////////////// var cube = new Cube(3, 6, 9); document.write(cube.getSize().toString());
The JavaScript code above is quite long. However, you should not worry about the source code size by simply looking at the number of characters entered.
Note that you should always write descriptive code for your documents, variable names, method names then let compiler rename or remove them later.
Similarly, don’t worry about having a three levels Class inheritance tree for such a simple function, the compiler will optimize that part for you.
Let’s see the compiled code below.
function d(a, b) { function c() { } c.prototype = b.prototype; a.prototype = new c } function e() { } e.prototype.a = function() { }; function f(a, b) { this.c = a; this.b = b } d(f, e); f.prototype.a = function() { return this.b * this.c }; function g(a, b, c) { f.call(this, a, b); this.d = c } d(g, f); g.prototype.a = function() { return this.d * this.b * this.c }; document.write((new g(3, 6, 9)).a().toString());
Though all the variables and method are renamed, you might notice that some methods are removed and some methods are in-lined. For example:
Cube.prototype.getSize = function() { return this.depth_ * this.getHeight() * this.getWidth(); };
Becomes:
g.prototype.a = function() { return this.d * this.b * this.c };
Apparently, the getter getWidth() and getHeight() can be replaced by this._width and this._height safely, so those getters become useless and can be removed by the compiler.
Note that using both @private and a getter implies that the private property _width is read-only for developers and there’s no penalty for adding a getter for it.
Use @interface and @implements
Since we’re interested in writing OO-style JavaScript, the previous example can be changed to code below.
// skip example code. //////////////////////////////////////////////////////////////////////////// /** * The shape * @interface */ function Shape() { } /** * Get the size * @return {number} The size. */ Shape.prototype.getSize = function() {}; //////////////////////////////////////////////////////////////////////////// /** * The Box. * @param {number} width The width. * @param {number} height The height. * @constructor * @implements {Shape} */ function Box(width, height) { Shape.call(this); /** * @private * @type {number} */ this.width_ = width; /** * @private * @type {number} */ this.height_ = height; } /** * @return {number} The width. */ Box.prototype.getWidth = function() { return this.width_; }; // skip example code.
The compiled code gets even smaller since @interface is only used in compiled time, and the output never includes the interface code.
function d(a, b) { this.c = a; this.b = b } d.prototype.a = function() { return this.b * this.c }; function e(a, b, c) { d.call(this, a, b); this.d = c } (function(a, b) { function c() { } c.prototype = b.prototype; a.prototype = new c })(e, d); e.prototype.a = function() { return this.d * this.b * this.c }; document.write((new e(3, 6, 9)).a().toString());
Use package (name-spaced JS Object.)
Namespace your JS Objects as long as you need, don’t worry about the level of the namespaces that you should use regarding runtime performance. The compiler takes care of that part for you.
// Create namespaces. var demo = {}; demo.example = {}; demo.example.exercise = {}; /** * @constructor */ demo.example.exercise.Foo = function() { demo.example.exercise.Foo.print(this.value1); demo.example.exercise.Foo.print(this.value2); }; /** * Static method * @param {string} str String to print. */ demo.example.exercise.Foo.print = function(str) { document.write(str); }; /** * @type {string} */ demo.example.exercise.Foo.prototype.value1 = 'abc'; /** * @type {string} */ demo.example.exercise.Foo.prototype.value2 = 'def'; var foo = new demo.example.exercise.Foo();
The compiled code looks like this:
function a() { document.write(this.a); document.write(this.b) } a.prototype.a = "abc"; a.prototype.b = "def"; new a;
Also, you can keep your JS code as private by using the flag –output_wrapper so it won’t collide with other scripts on the page and none of your JS object is global (unless you export them explicitly)
The compiled code looks like this:
(function() {function a() { document.write(this.a); document.write(this.b) } a.prototype.a = "abc"; a.prototype.b = "def"; new a;})()
The compiler will do its best