In World of Raids Guild Recruitment Tool, we wanted the search to be instant! We achieved this goal by not having a single page refresh or ajax call while updating the settings. Since it runs in the browser, performance was a real concern. We will see in this article what were the tricks used to make this real.

Sorting

In order to sort the guilds, each guild is given a matching score (between 0 and 65535) based on the current filters. At start, each guild has the maximum points. Then, each filter is giving a malus. For example when a specialization doesn't match, the guild loses 10%.

This means that everytime you change a filter, you have to recompute that matching score for all the guilds. Instead of recomputing the whole matching score, the guild holds the result of each filter. Everytime a filter is updated, the value of this filter is upated on every guild. The global matching score is now recalculated by summing all the partial precomputed scores.

Every bit of code in the matching function has been optimized, no more jquery code, loops are unrolled and great use of lazy boolean expression evaluation. The sort function itself also got boosted thanks to a trick I commented on my previous article Speed Up Javascript Sort().

Guild Recruitment Search Filters

Display

Once we have the data sorted, it has to be displayed! And this is another big time eater. When it takes 50ms to sort the data, it takes about 150ms to display 10 guilds!

Since we wanted performances, DOM elements are created at page loading, they are then only being updated as the guilds change. And still, only the required changes are made, for example tooltips are created as the user trigger the mouseover event.

The main problem with this is that we are not able to make bulk changes. Everytime you make the slightest change, the whole UI has to be redrawn which is a waste of time. I hope there will be such API in the future.

Guild Display

Since it's a web application, we had to maintain the url up to date, this is done using SmallHash, a small library of mine that compresses filter data into the smallest possible hash.

Scaling

Having all the data being sent at page loading is a pain, it makes it load slowly. One way to avoid this problem would be to use the localstorage to keep these data accross sessions and only send the updated guilds. However at the time I started developping the application there was no released browser supporting HTML5.

However, the application is focused on World of Warcraft guilds. This is a limited audience and there is no need on heavy scaling there. So we decided that it was a good option to go.

Up to 3000 guilds, this is pretty much instant in every browser in my MacBook Pro. When increasing that number to 10 000 it is taking more than a second to update on IE8. It requires 50 000 guilds for Chrome 3 to get slow, which is a pretty good score. It means that it is possible to do heavy processing, the bandwith now being the limiting factor.

,

Working on the World of Raids Recruitment Tool we wanted automatic saving while editing. There are basically two ways of handling the problem.

  • Send update everytime something changes. The main advantage of this technique is that you are always up to date. However, it is spamming the server on fields that are constantly being updated (text fields for example).
  • Send update every X seconds. There is no longer query spike but there are two main disadvantages.

    The delay the data are sent after a modification can vary from nothing to the timer length for no apparent reason. This is a really bad feeling for the user. He will think that the application is buggy.

    The application is going to send queries all the time, even if there are no changes. We can safely assume that people are interacting a lot less than viewing so this is a real waste of resources.

We are interested in the first one, we want instant update but removing the ability to send too much data at once. What we want is to send data right after the user is done typing instead of everytime a character is typed. There is an easy way to do it in Javascript.

When you type the first character, it will start a timer of let say 100ms. Then everytime you type another character it resets the timer to 100ms. When it triggers, it will send data to the server.

This way, data will only be sent when the user interacts and with no risk of spamming the server.

var Object = {
  /* Object Code ... */
 
  timer: null,
  sendChanges: function() {
    if (this.timer) {
      clearTimeout(timer);
      this.timer = null;
    }
 
    this.timer = setTimeout(
      function () { /* Send Changes */ },
      200 /* Timeout in ms */
    );
  }
};

Comparison

In Javascript there are 3 types we are often comparing: String, Number and Boolean. After digging through the ECMA-262 specifications, here is the behaviour of the == operator (11.9.3) on these types:

  • Number == String
    Typecasted as follow: Number == Number(String)
  • Number == Boolean
    Typecasted as follow: Number == Number(Boolean)
  • String == Boolean
    Typecasted as follow: Number(String) == Number(Boolean)

This means that when comparing data of two different types, they will first be converted to Number.

Note: The order of the equality is not important: A == B is the same as B == A (except the order of evalution of A and B).

You can force comparison of a and b with the type you want:

  • String: "" + a == "" + b
  • Number: +a == +b
  • Integer: a | 0 == b | 0
  • Boolean: !!a == !!b

Addition / Concatenation

The binary + operator follows a simple rule (11.6.1):

  • if one of the operands is a String, both operands are converted to String and the + is a concatenation.
  • Else, both operands are converted to Number and the + is an addition.

Note: Some Objects are considered as Strings like Arrays. See 8.6.2 for more informations.

Note: The operator is binary so 1 + 1 + 'a' will be executed as (1 + 1) + 'a' and therefore be equal to "2a" and not "11a".

Javascript is a very flexible language, I made a compilation of some edge cases that you may have encountered while programming. The main goal is to point out some interesting specific behaviors.

Concatenation

1] var result = [10] + 1;
Explanation:
The Array doesn't have a toNumber method, instead it has a toString method: [10] + 1 becomes "10" + 1.
String has a greater priority over Number with the + operator: "10" + 1 becomes "10" + "1" = "101"
2] var result = ['a', 'b', 'c'] + "";
Explanation:
The Array.toString method concatenates all it's elements (converted to string) with the "," separator.
3] var result = 'a' + 5;
Explanation:
In Javascript, String has a greater priority than Number for the + operator, so 5 is converted to string before being concatenated.
In PHP, the + operator is only the addition so 'a' is converted to number (gives 0) before being added.
In C, a char and an integer are of the same type. 'a' is first converted to it's Ascii value (gives 65) before being added.

Operations

4] var result = 3.75 | 0;
Explanation:
|0 is a fast way to do a Math.floor for positive integers.
5] var result = 65 / 'a';
Explanation:
/ is only defined for Numbers so 'a' is first converted to a number (gives 0).
A division by 0 does not throw an error but results the object NaN (Not a Number).

Objects

6] var ob = {"10": 1};
ob[10] = 2;
ob[[1, 0]] = 3;
var result = ob["10"] + ob[10] + ob[[1, 0]];
Explanation:
Objects keys are strings. If you don't provide a string, it will convert the key to string.
"10" and 10 gives "10"
[1, 0] gives "1,0"
7] var $ = {"": String};
var result = !!$[([])]();
Explanation:
$ is a valid variable name.
Parenthesis inside the key part are useless: array[(1+2)] is the same thing as array[1+2].
Objects key are first converted to string. [].toString() is "".
It is possible to have the empty string "" as an object key.
$[""] is the String object. String(val) returns val.toString(). String() returns "".
! is the negation operator. "" == false, so !"" == true, !!"" == false
!!expr is a fast way to typecast an expression to a boolean.

Equality

8] var result = (' \t\r\n ' == 0);
Explanation:
Unlike PHP, strings wrapped around simple quote ' are also parsed. '\t' == "\t"
When compared to a number value, a string will be converted to a number. If it contains only spacing characters it will be converted to 0. If a string cannot be parsed as a number it will return NaN (Note that NaN != NaN).
9] var a = new String("123");
var b = "123";
var result = (a === b);
Explanation:
When creating an object with the new operator, the result type is always "object". The type of "123" is "string" so the type does not match for ===.
10] var a = {key: 1};
var b = {key: 1};
var result = (a == b);
Explanation:
Object comparison is only done with the pointers behind the objects.


The bracket notation for string is incomplete in Javascript and does not work in IE7. This is really painful to migrate to the .charAt(pos) equivalent, this is why i recommend you not to use it.

// Bracket Notation
"Hello World!"[6]
// > "W"
 
// Real Implementation
"Hello World!".charAt(6)
// > "W"

The bracket notation to get a character from a string is a shortcut to .charAt(pos) added by the vast majority of the browsers. However, i would not recommend to use it for several reasons.

This notation does not work in IE7. The first code snippet will return undefined in IE7. If you happen to use the bracket notation for strings all over your code and you want to migrate to .charAt(pos), this is a real pain: Brackets are used all over your code and there's no easy way to detect if that's for a string or an array/object.

You can't set the character using this notation. As there is no warning of any kind, this is really confusing and frustrating. If you were using the .charAt(pos) function, you would not have been tempted to do it.

var string = "Hello World!";
string[6] = '?';
console.log(string);
// > "Hello World!";