Recently in Code Category

CommonJS require(..) API is a poor fit for client side JS

By Luke Smith on November 24, 2009 10:54 PM

This post is in response to David Flanagan's client side JS implementation of CommonJS's module pattern/API.

There are several performance considerations with script/module/dependency loading.

  1. script tags in markup block the page progression
  2. synchronous XHR blocks the js thread and therefore the UI responsiveness
  3. script tags added to the DOM via js do not block page progression, but their execution order must be managed
  4. there are severe performance penalties for numerous http requests (http meta/overhead, time, and browser's concurrent resource request limits)
  5. "discovered" dependency resolution == chained http requests (sync or async), so deep deps trees could take a long time to resolve. A reqs B reqs C could result in load A, then load B, then load C, then execute C, then B, then A, then execute requiring code (see #2 and #4).

The CommonJS API has great qualities for environments that don't suffer from the sorts of IO costs and conditions that make sync behavior a Bad Idea(tm) in client side scripting. The implementation as one-expression-per-requirement is especially bad because it is functionally equivalent to multiple blocking script nodes locking up the page progress or rendered UI, which is somewhere between bad and horrible. To avoid this, require might be a registration function of an async loader to bundle multiple require(..) calls from the same thread, but that breaks the require-then-use contract.

The YUI 3 module and use(..) arch is basically an async implementation of key parts of this API. This is especially obvious in the YUI.add(moduleName, function (Y) { /* add stuff to Y */ }, ver, meta); form of registering modules where you could easily substitute export for Y.

The big differences are that Y.use(..) accepts multiple modules/requirements to avoid the one-expression-per-requirement, it does pre-fetch dependency resolution based on a managed dependency metadata tree, and the modules are loaded asynchronously using as few requests as possible (the Yahoo! CDN combo service makes 1 request the common case), executing a callback when complete.

The less than optimal part of this is the dependency resolution tactic, especially for modules from third parties. Delaying dependency calculation for a module until that module is loaded would kill performance, so all dependency metadata for library modules is stored in a meta module for pre-fetch resolution. We already know this won't scale in a non-impactful way, and of course this doesn't address third party modules. For third party modules, we require dependency metadata in the configuration for requiring code, which is less than ideal. For optimal performance and client side footprint, dependency resolution basically has to happen on the server side or be integrated into a pre-deployment build step.

All that said, my main point is that as exciting as the recent server-side JS movement has been, the synchronous require(..) pattern is a poor fit on the client side for the high performance demands of today's sites and RIAs.

Test if a font is installed via JavaScript

By Luke Smith on May 26, 2009 8:45 AM

There are a few threads available on the web with regard to how to test if a font is installed on a client machine, but I'm not satisfied with any of those that I found. The reason being that they each seem to use a single common font as a baseline measurement and compare the dimensions of some reasonably complex string rendered in the common font against the same string rendered in the test font.

This irks me for a number of reasons:

  1. It assumes the common font is installed
  2. It only confirms the dimensions of the tested font don't coincide with the common font
  3. All of those I saw rerendered the same element over and over, triggering an unnecessary number of reflows

If you want to check if a font is installed, start with a provable test. The system default monospace font should render a string in different dimensions than the system default sans-serif font. But you can test this.

(function () {
var div = document.createElement('div'),
    different = false;

div.innerHTML = '<span style="...;font-family: sans-serif">some string</span>' +
                '<span style="...;font-family: monospace">some string</span>';

document.body.insertBefore(div, document.body.firstChild);

different = div.childNodes[0].offsetWidth != div.childNodes[1].offsetWidth;

document.body.removeChild(div);

alert(different);
})();

Using this tested assertion as a baseline, let the browser tell you if the test font is installed by using the default behavior of font stacks. Create two elements with the same content ("ii" has been sufficient in my tests), each styled with a font-family of the test font followed respectively by the two control fonts that you know to render to different dimensions.

<b style="font: normal 10px/1 'FONT_X', sans-serif !important">ii</b>
<b style="font: normal 10px/1 'FONT_X', monospace !important">ii</b>

The two elements will render to the same dimensions if and only if the the font is installed.

Here's my approach:

function testFont(name) {
    name = name.replace(/['"<>]/g,'');

    var body  = document.body,
        test  = document.createElement('div'),
        installed = false,
        template =
            '<b style="display:inline !important; width:auto !important; font:normal 10px/1 \'X\',sans-serif !important">ii</b>'+
            '<b style="display:inline !important; width:auto !important; font:normal 10px/1 \'X\',monospace !important">ii</b>',
        ab;

    if (name) {
        test.innerHTML = template.replace(/X/g, name);

        test.style.cssText = 'position: absolute; visibility: hidden; display: block !important';

        body.insertBefore(test, body.firstChild);

        ab = test.getElementsByTagName('b');

        installed = ab[0].offsetWidth === ab[1].offsetWidth;

        body.removeChild(test);
    }

    return installed;
}

The code above assumes the string "ii" renders to different width in monospace than sans-serif, but the important part is that this can be tested if you don't feel comfortable with that assumption. Additionally, containing two elements in an absolutely positioned div should limit the scope of the two requisite reflows if the browser is smart about it.

If you want to test more fonts, create more template entries in the innerHTML all at once before attaching the div to the DOM, then just loop through the entry pairs. This will preserve the two reflow impact.

Here's a test page.

And here's the code in a GitHub gist. It's likely this is more up to date than the snippet above.

Factory/Constructor that accepts N args

By Luke Smith on May 20, 2009 7:38 PM

I'm a big fan of the Factory/Constructor pattern in JavaScript.

function Foo() {
    if (!(this instanceof Foo)) {
        return new Foo();
    }

    // the rest of your initialization code
}

Foo.prototype = { ... };

var f1 = new Foo(), // f1 instanceof Foo === true
    f2 = Foo(); // f2 instanceof Foo === true

But this runs into a problem when you want your constructor to accept any number of arguments.

Function Person(mother, father) {
    this.mother = mother;
    this.father = father;
    this.children = [].slice.call(arguments,2);
}

Apply invocation vs Constructor invocation

Unfortunately, you would need to invoke the constructor via Person.apply(arguments) to pass all the arguments in individually, but that doesn't create an instance of Person since it wasn't called via new. You can get around this by referring to a local variable in your constructor that is either this or a new empty instance, then applying the changes to whatever is in the variable and returning it.

Function Person(mother, father) {
    var self = (this instanceof Person) ? this : new Person();

    self.mother = mother;
    self.father = father;
    self.children = [].slice.call(arguments,2);

    return self; // important
}

When a constructor returns an object, the return statement is honored over the default behavior (sans return statement) of returning a class instance.

But this doesn't pan out if it is inappropriate for the class to be called without arguments or if there's some complex logic that needs the seed info in order to make sense.

Quite a list of requirements

Now, at this point any reasonable person would probably rethink the architecture that necessitated such a construct (seriously, is it really a requirement to also support function invocation?), but a) I'm not a reasonable person, and b) hey, fun with code! So here goes.

Circling back to the point that returning an object from a constructor will trump the default behavior, we can just defer the construction behavior to another constructor function.

function Person(mother, father) {
    return new _Person(arguments);
}

function _Person(_) {
    this.mother = _.shift();
    this.father = _.shift();
    this.children = _;
}

Now it doesn't matter how you execute the Person function. It will return an instance of _Person, which is set up with the initialization code in its constructor.

But I didn't ask for a _Person

Now there are two things left to attend to: 1) the returned object isn't an instance of Person, and 2) we now have two constructor functions exposed to our consumers.

var Person = (function () {
    function C(mother, father) {
        return new _Person(arguments);
    }
    function _Person(_) {
        this.mother = _.shift();
        this.father = _.shift();
        this.children = _;
    }

    C.prototype = _Person.prototype = { ... };

    return C;
})();

Assigning _Person's prototype to C (aka Person) makes instances pass the instanceof test and allows for modifying the class prototype with the expected outcome. And defining the helper constructor in a closure hides it from the implementation layer.

Overkill? Almost certainly. But JavaScript is a fun language, and who knows? Maybe this pattern is just the right thing for some use case out there. Ok, maybe not :)

Another js function to insert commas in numbers

By Luke Smith on December 14, 2008 9:52 PM

This evening's academic exercise was to write your basic 1234 => 1,234 function as terse as possible, and without a loop. At first I thought I could do it with just a clever regex, and maybe I could have, but I abandoned that approach.

This is what I ended up with:

function formatNumber(n) {
    if (!isFinite(n)) {
        return n;
    }

    var s = ""+n, abs = Math.abs(n), _, i;

    if (abs >= 1000) {
        _  = (""+abs).split(/\./);
        i  = _[0].length % 3 || 3;

        _[0] = s.slice(0,i + (n < 0)) +
               _[0].slice(i).replace(/(\d{3})/g,',$1');

        s = _.join('.');
    }

    return s;
}

I could have omitted the storage of Math.abs(n) and the short circuit for numbers not needing comma injection, but eh. No need to sacrifice runtime performance for a few characters.

The basic principle is to use string replace to insert commas on only the chunk of the number that needs them. intAsString.length % 3 || 3; will return the index of the first character that needs a comma before it (for positive values, hence the abs), so everything beyond that point needs to be divided into three digit chunks, each preceded by a comma.

Update - 1/16/2009

I happened across a similar function, based on PHP's number_format function. It prompted me to adapt in the extra params to support precision and specified characters for decimal and thousands divider.

I put the result in a gist. I put together this page to confirm they were behaving comparably.

Update - 3/18/2009

Updated to account for input 1000. > should have been >= Oops! Thanks to Simon for helping me notice that :)

I also put the barebones function in a gist

Add business days to a Date

By Luke Smith on November 18, 2008 8:11 PM

This morning in ##javascript, a visitor asked how to add n business days to a Date instance. Call me a sucker, but I love little tasks like this. Maybe I just get antsy when I smell a potential modulo.

I thought I would be able to find such a function pretty quickly online, but I wanted to write it first, then validate my approach against what I found. Here's what I came up with:

function addBusinessDays(d,n) {
    d = new Date(d.getTime());

    var day = d.getDay();

    d.setDate(
        d.getDate() + n +
        (day === 6 ? 2 : +!day) +
        (Math.floor((n - 1 + (day % 6 || 1)) / 5) * 2));

    return d;
}

Not terribly readable, but it works. You could omit the first line if you have no desire to preserve the input Date object. And it is hard coded to assume "business days" are between Monday and Friday. If you wanted to offset the resulting date for holidays, you could maintain an object literal keyed by a serialization of holiday dates, and in the function check against the object, rerunning the algo with n + 1 if a match was found.

Compare and contrast

It turned out after finishing the code, I had little luck finding a comparable function in the wild. Perhaps my search criteria were awful or there's not actually a market for such a function, but my findings were sparse. The closest thing I found was this gargantuan monster. I was surprised that such a function didn't show up in the DateJS lib. Regardless, both collections of functions modify the Date.prototype which I don't do.

So either I need to hone my search skills, or my efforts may have actually been worthwhile.

Here's the function minified…you know, in case you wanted to save 25b over the wire or something:

function addBusinessDays(B,C){B=new Date(B.getTime());var A=B.getDay();B.setDate(B.getDate()+C+(A===6?2:+!A)+(Math.floor((C-1+(A%6||1))/5)*2));return B;}

And a demo page showing a variety of dates and a delta range of -10 to +10.

Update 10/14/2009

I've moved this to a GitHub project and included a couple new versions. One to modify the Date in place and two others that adjust for holidays (some assembly required).

ls.n

LucasSmith.name

Luke and Liam

I'm Luke. I am a front end engineer at Yahoo! on the YUI team.

Mostly I write about code stuff, but occassionally I'll mix in some real life. You've been warned.

Archives

Tags

Feeds

Subscribe to feed Recent entries

Content licensed under Creative Commons

Code licensed under BSD license

©2005 - 2010 Lucas Smith

Powered by Movable Type