2006-12-31

A bug of Douglas Crockford's uber

In Douglas Crockford's famous Classical Inheritance in JavaScript, he write a function named 'uber' which simulate 'super' for OO programming. Someone pointed out a bug of it, but don't understand why and just try to write his own OO solution.

Below is the test case:


function BaseClass() {}
BaseClass.prototype.getName = function() {
    return "BaseClass(" + this.getId() + ")";
}
BaseClass.prototype.getId = function() {
    return 1;
}

function SubClass() {}
SubClass.inherits(BaseClass);
SubClass.prototype.getName = function() {
    return "SubClass(" + this.getId() + ") extends " +
        this.uber("getName");
}
SubClass.prototype.getId = function() {
    return 2;
}

function MyClass() {}
MyClass.inherits(SubClass);
MyClass.prototype.getName = function() {
    return "MyClass(" + this.getId() + ") extends " +
        this.uber("getName");
}
MyClass.prototype.getId = function() {
    // Should always return 2 which is the result of SubClass.getId()
    return this.uber("getId");
}

alert(new TopClass().getName());

//Expect result: "MyClass(2) extends SubClass(2) extends BaseClass(2)"
//Actual result:"MyClass(2) extends SubClass(1) extends BaseClass(1)"

I did some research about this interesting bug, and got the patch.

Douglas Crockford's original source


Function.prototype.inherits = function (parent) {
    var d = 0, p = (this.prototype = new parent());
    this.prototype.uber = function (name) {
        var f, r, t = d, v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d -= 1;
        return r;
    }
    return this;
}

My patched version


Function.prototype.inherits = function(parent) {
    var d = {}, p = (this.prototype = new parent());
    
    this.prototype.uber = function(name) {
     if (!(name in d)) d[name] = 0;
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d[name] += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
    }
}

The problem is because of d, which indicates the depth of supercall. In original code, every method share the same d, which will cause bug when nest call (one method call another). So give every method their own counter, solves the problem.

String format function for JavaScript

Note: This artical is the republication of my two posts A high performance string format function for JavaScript and Just another high performance string format function for JavaScript on csdn blog.


Last month, I wrote a logging tool for js, and to avoid performance depression, I need a string formatter function.

I found that though many js toolkit or framework provide string format function, such as Atlas, but they r not quite fast. Most r using String.replace(regex, func) to replace the placeholder(such as '{n}' or '$n'), but this function tend to slow, because every match will call the func, and js function calling is very expensive.

On the contrary, native functions r very fast, so to improve performance, we should utilize the native functions as possible as we can. A wonderful example is StringBuilder which use Array.join to concat the string.

So I create my String.format(), it's very fast.

Usage:


var name = 'world';
var result = 'Hello $1!'.format(name);
// result = "Hello world!"

var letters = String.format(
 '$1$2$3$4$5$6$7$8$9$10$11$12$13$14$15\
 $16$17$18$19$20$21$22$23$24$25$26',
 'a', 'b', 'c', 'd', 'e', 'f', 'g',
 'h', 'i', 'j', 'k', 'l', 'm', 'n',
 'o', 'p', 'q', 'r', 's', 't',
 'u', 'v', 'w', 'x', 'y', 'z');
// letters = "abcdefghijklmnopqrstuvwxyz"

The later one almost same fast as the former one, no other implementation can have the same performance as I know.

Note:

  • It's depend on String.replace(regex, string), so u can use at most 99 placeholder($1 to $99), but if the script engine is too old, it maybe only support nine ($1 to $9) or not work at all (eg. JScript before 5.5?).
  • literal $ should be escape into $$ (two $).
  • $` and $' will be reomoved, and $& will replaced into some strange things :)
  • '$1 1'.format('a') result in 'a 1', if you want to strip the space, u can't write '$11'.format(...) because it will try to match the 11nd parameter, u should write '$011'.format(...) instead.
  • There is a magic character which you can't use anyway, currently I choose 0x1f (which means data separator in ascii and unicode).

Source code:


// Copyright (c) HE Shi-Jun , 2006
// Below codes can be used under GPL (v2 or later) or LGPL (v2.1 or later) license

if (!String._FORMAT_SEPARATOR) ...{
    String._FORMAT_SEPARATOR = String.fromCharCode(0x1f);
    String._FORMAT_ARGS_PATTERN = new RegExp('^[^' + String._FORMAT_SEPARATOR + ']*'
      + new Array(100).join('(?:.([^' + String._FORMAT_SEPARATOR + ']*))?'));
}
if (!String.format)
    String.format = function (s) ...{
    return Array.prototype.join.call(arguments, String._FORMAT_SEPARATOR).
    replace(String._FORMAT_ARGS_PATTERN, s);
}
if (!''.format)
    String.prototype.format = function () ...{
    return (String._FORMAT_SEPARATOR +
    Array.prototype.join.call(arguments, String._FORMAT_SEPARATOR)).
    replace(String._FORMAT_ARGS_PATTERN, this);
}

Below is just another format function:


// Copyright (c) HE Shi-Jun , 2006
// Below codes can be used under GPL (v2 or later) or LGPL (v2.1 or later) license

format2.cache = ...{};
function format2(pattern) ...{
    if (!(pattern in format2.cache)) ...{
        format2.cache[pattern] = new Function('"' + pattern.replace(/"/g, '\"').replace(/$([0-9]+)/g, '" + arguments[$1] + "').replace(/$$/g, '$') + '"');
    }
    return format2.cache[pattern](arguments);
}

Compare to previous method, it's even more fast in heavy using (especially on FireFox and Opera), because it's compile the pattern to function and cache it. But this method will waste memory. So the best practice is combining these two methods.

And the no cache version here, but not helpful, because it's lose the advantage of cacheable and will be very slow on Opera:


// Copyright (c) HE Shi-Jun , 2006
// Below codes can be used under GPL (v2 or later) or LGPL (v2.1 or later) license

function format3(pattern) ...{
    return eval('"' + pattern.replace(/"/g, '\"').replace(/$([0-9]+)/g, '" + arguments[$1] + "').replace(/$$/g, '$') + '"');
}

2006-12-27

Keep connection

The seaquake occured yestereve(2006-12-26T20:25+8:00) cause at least sixeight cables damaged. It affects the internet connections from China to USA and Europe. Most people, include me, can't access MSN, Yahoo, MySpace and many other sites. As a MSN user, I'm very worry about losing the connection to the world in the next two weeks.

But I'm pleased that I can still connect most Google services, such as gmail, gtalk, google reader, and blogger. Someone said that is because Google has some servers at China. But I doubt whether there are some servers of blogger at China? Another guess is Google services are mainly base on the continent cables not the sea cables. The result of TraceRoute support this.

Not surprisingly, I have read the arguments about .com domain and drum for .cn domain again, which says .cn is controlled by chinese people ourselves, and won't be affected by such accident. Ridiculous, let's make our own China Intranet, put off Internet...

Anyway, It's a good opportunity to prove my choice about blogger is reasonable.

2006-12-25

Hello world

For every programmer, it's the first step to write "Hello world" case. Here is mine.

With a new year coming, I finally start this new blog. In past days, I am maintaining three blogs, csdn, sfo and my-donews. All three are in chinese. Though I wrote a few posts in english, but the UI(web pages) of them are in chinese which may affect sth (I'm not sure).

Anyway, I want to be more open to the whole worlds, esepecially in the technique fields. Until now there are too many blog host sites(eg. wordpress.com) are fobidden by China GWF. Fortunately, (google do sth and) blogger comes back for china users. It's one of the most famous and the best blog host, and it's an international place. So I'm here.

To be honest, I will still post many posts in chinese, but at least with an additional title and abstract in english.

BTW, after register, what I first do is changing the settings of blogger, which default to localized chinese interface. Hum... It's not as easy as I thought. There are two, one is settings->formatting->language and the other is dashboard->change language. The latter determines the UI language of author's manage dashboard, but the lang used by toolbar on top of the pages also depends on it. It's obviously a wrong design, because the lang settings of the author should not affect the readers.