Tuesday, April 19, 2005 |
|
|
This is hopefully the first of a series of posts about object-oriented programming in JavaScript. You can’t get far in JavaScript without learning about functions and the built-in object types, but the mechanisms for defining your own object types aren’t quite as obvious. Let’s start as simple as we can – we’ll create an object that represents a two-dimensional point: var pt = {};
pt.X = 3;
pt.Y = 4;
No surprises there. The pt object has two properties, X and Y. We can write functions that manipulate points; for example: function MovePoint(pt, dx, dy)
{
pt.X += dx;
pt.Y += dy;
}MovePoint(pt, 1, 2);
Of course, we’d rather have a Move method on the pt object itself. pt.Move = function (dx, dy) { this.X += dx; this.Y += dy; };pt.Move(1, 2);
When a function is assigned to a property of an object, and that function is called using the syntax shown, the special this variable references that object, so that when pt.Move is called, this.X refers to the X property of pt.
Now, it would be rather inconvenient to type all of that every time we need a new point, so we can write a function that creates a point. function CreatePoint(x, y)
{
return { X : x, Y : y, Move : Point_Move };
}function Point_Move(dx, dy)
{
this.X += dx;
this.Y += dy;
}var pt = CreatePoint(3, 4);
pt.Move(1, 2);
I’ve also taken the liberty of creating a Point_Move function so that a new anonymous function isn’t created every time a new point is created. (The actual name of the Point_Move function isn’t relevant; I could have called it Krebf, but the Point_Move naming convention helps me remember that I’m using it as the Move method for a Point.)
This actually works quite well, but it would be more natural if we could use the new operator, as with built-in objects. Fortunately, this is easy enough to do – we just create a function that will serve as the constructor. function Point(x, y)
{
this.X = x;
this.Y = y;
this.Move = Point_Move;
}var pt = new Point (3, 4);
pt.Move(1, 2);
The two approaches do almost exactly the same thing; new Point creates a new object and immediately calls the Point function as if it were a member of the new object. There are subtle (and useful) differences that I’ll describe in my next post. |
4/19/2005 3:44:13 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Wednesday, April 06, 2005 |
|
|
Have you ever wanted to determine programmatically the name of the function represented by a function object? There’s no direct way to get it, but there is an indirect way. The toString method of a function object returns the function declaration, from the function keyword at the start all the way to the last closing brace. Even “native” functions support the toString method in this fashion, though the implementation is omitted. This means that a simple regular expression can be used to determine the name of any function. function GetFunctionName(fn)
{
var m = fn.toString().match(/^\s*function\s+([^\s\(]+)/);
return m ? m[1] : "";
}
The regular expression simply finds the identifier after the function keyword. If it isn’t there (as can be the case with anonymous functions) GetFunctionName returns the empty string. |
4/6/2005 3:22:20 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Tuesday, April 05, 2005 |
|
|
My last post described the BindArguments function – it returns an anonymous function that, when called, calls another function with the arguments originally specified in the call to BindArguments. Put another way, BindArguments converts a function that takes n arguments into a function that takes 0 arguments. But what if we need a function that takes 1 argument?
Imagine we have a Transform function that transforms an array of numbers in place: function Transform(array, fn)
{
ForEach(array, function (x, n) { array[n] = fn(x); });
}
We want to use this Divide function to do the transformation. function Divide(n1, n2)
{
return n1 / n2;
}
If we want to invert the numbers in the array: Transform(array, function (x) { return Divide(1, x); });
Can we write a binding function like BindArguments that allows us to avoid the explicit anonymous function? BindArguments won’t work, because it returns a function that takes 0 arguments. If the returned function is called with any arguments, those arguments are ignored. We need the returned function to accept an argument and call Divide with 1 and that argument. We’ll call it BindFirst, because it accepts a function that takes two arguments and binds the first one. Transform(array, BindFirst(Divide, 1));
In other words, BindFirst(fn, x)(y) === fn(x, y)
Can we write such a function? Of course! function BindFirst(fn, arg)
{
return function (x) { return fn.call(this, arg, x); };
}
We can do better and create a function that will bind any number of arguments before any number of additional arguments. // BindFirst(fn, x1, x2, …, xn)(y1, y2, …, yn) === fn(x1, x2, …, xn, y1, y2, …, yn) function BindFirst(fn)
{
var args = [];
for (var n = 1; n < arguments.length; n++)
args.push(arguments[n]);
return function ()
{
var myargs = [];
for (var m = 0; m < arguments.length; m++)
myargs.push(arguments[m]);
return fn.apply(this, args.concat(myargs));
};
}
Outstanding. But wait; what if we want to divide the numbers in our array by 2? Transform(array, function (x) { return Divide(x, 2); });
What we need here is a BindSecond function. // BindSecond(fn, x)(y) === fn(y, x) function BindSecond(fn, arg)
{
return function (x) { return fn.call(this, x, arg); };
}Transform(array, BindSecond(Divide, 2));
Better yet, BindLast, whose implementation I leave as an exercise for the reader. (It is very similar to that of BindFirst.) // BindLast(fn, x1, x2, …, xn)(y1, y2, …, yn) === fn(y1, y2, …, yn, x1, x2, …, xn)
Is it possible to write a truly generic Bind function that can be used in place of both BindFirst and BindLast and support even more complex binding scenarios? It is, and I hope to show it to you… in a future post. |
4/5/2005 12:31:44 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Thursday, March 31, 2005 |
|
|
When you start passing functions around, you often end up creating anonymous functions that call other functions with specific arguments.
For example, let’s suppose that we’re writing a DHTML calculator, and we have a function WriteDigit that writes the specified digit to the display. To set the onclick handlers of the calculator buttons, we could do something like this: btnOne.onclick = function () { WriteDigit(1); };
btnTwo.onclick = function () { WriteDigit(2); };
That will work just fine, but there is another way. We can write a function that takes an integer as its argument and returns the anonymous function. function BindWriteDigit(n)
{
return function () { WriteDigit(n); };
}btnOne.onclick = BindWriteDigit(1);
btnTwo.onclick = BindWriteDigit(2);
Why would we do such a thing? Well, one good reason is to avoid unwanted closures. Whenever you create an anonymous function, you’ve got to pay attention to the local variables that will be kept alive, particularly under Internet Explorer, as I alluded to in a previous post. In general, when you are creating anonymous functions that will outlive the function that is creating them, you should minimize the number of local variables in that surrounding function to prevent unwanted closures, accidental modification of local variables used by the anonymous function, and other surprises.
It would be a pain to write a separate binding function for every function that we want to bind; fortunately, we can write a function that takes a function as its first argument and returns an anonymous function that calls that function with its second argument. function BindArgument(fn, arg)
{
return function () { return fn(arg); };
}btnOne.onclick = BindArgument(WriteDigit, 1);
btnTwo.onclick = BindArgument(WriteDigit, 2);
No need to stop with one argument, though; with a little more effort, we can write a function that will bind to any number of arguments by using the apply method. function BindArguments(fn)
{
var args = [];
for (var n = 1; n < arguments.length; n++)
args.push(arguments[n]);
return function () { return fn.apply(this, args); };
}btnOne.onclick = BindArguments(WriteDigit, 1);
btnTwo.onclick = BindArguments(WriteDigit, 2);
This technique comes in very handy, particularly in problems that are less contrived than this one. You can do a lot more than this with a sufficiently complex binding function; more on that in a future post.
Update: Fixed a bug in BindArguments. (Thanks, Bradley!) |
3/31/2005 1:50:13 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Wednesday, March 30, 2005 |
|
|
As I mentioned before, for...in would be a convenient way to enumerate arrays, if it actually worked the way you’d expect it to. Fortunately, anonymous functions make it possible to write a ForEach method that’s almost as easy to use as a proper foreach. function ForEach(array, fn)
{
for (var n = 0; n < array.length; n++)
fn(array[n]);
}
As you can see, ForEach calls the specified function for each element of the array. So if you’d like to calculate the sum of the items in an array: function SumArray(array)
{
var nSum = 0;
ForEach(array, function (n) { nSum += n; });
return nSum;
}
I hope you agree that this syntax is not only more compact than an explicit loop, but that it is also easier to understand. It is also less efficient, of course, but not significantly so, except in special cases where you would certainly measure to determine its impact; you could easily unravel it into a standard loop if necessary.
My preferred implementation of ForEach is slightly more complicated. function ForEach(array, fn, objThis)
{
objThis = objThis || this;
var len = array.length;
for (var n = 0; n < len; n++)
{
var r = fn.call(objThis, array[n], n);
if (r !== undefined)
return r;
}
}
Let me explain:
- Calculating the array length outside the loop can be slightly faster.
- I pass the item index as the second argument to the called function, since that can often be useful. (The called function doesn’t need to declare it as an explicit argument if it doesn’t need it.)
- If the called function returns anything but
undefined, the loop is terminated. This allows us to simulate what would be a break statement in a normal for loop. The ForEach method returns the value returned by the called function.
- If the called function returns
undefined, the loop continues. (A function returns undefined if it gets to the end without hitting a return statement, if it hits a return statement without an expression, or if it hits a return undefined (naturally).) An explicit return statement without an expression thus simulates what would be a continue statement in a normal for loop.
- Using the
call method allows us to specify this for the called function, which can be useful when calling ForEach from an object method.
- The
objThis argument is optional, but I make sure that it is never undefined, because under Internet Explorer, the call method is slightly faster when the first argument is a valid object (passing undefined forces it to determine the “global” object itself).
For example, we can use ForEach to find an item in an array, returning the index of that item, or -1 if the item is not found. function FindInArray(array, find)
{
var index = ForEach(array,
function (item, n)
{
if (item == find)
return n;
});
return index === undefined ? -1 : index;
}
In many cases (but not all) I have found that using ForEach is easier to write and more natural than an explicit loop. YMMV.
Update: Explicitly described how to simulate a continue; thanks to Eli for his comment. |
3/30/2005 8:53:48 AM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Monday, March 28, 2005 |
|
|
Let’s say you want to sort an array of integers. var array = [ 3, 14, 15, 9, 26 ];
You might think that you could just use the sort method of the Array. You’d be wrong. array.sort(); // [ 14, 15, 26, 3, 9 ]
The sort method of an Array always sorts strings, which produces the unexpected result shown above. However, you can pass a function to the sort method. function CompareIntegers(x, y) { return x - y; }
array.sort(CompareIntegers); // [ 3, 9, 14, 15, 26 ]
Now we’re talking. The CompareIntegers function returns a positive integer if x > y, a negative integer if x < y, and zero if x == y, which is what we needed for the sort method.
What if we wanted to sort the numbers in reverse order? Well, we could define a CompareIntegersDescending function, but it’s easier to use an anonymous function. array.sort(function (x, y) { return y - x; }); // [ 26, 15, 14, 9, 3 ]
Very convenient. Here’s a function that can be used to find an item in an array. function Find(array, fn)
{
for (var n = 0; n < array.length; n++)
if (fn(array[n]))
return n;
return -1;
}
The first argument is the array to search; the second argument is a function that returns true when the item has been found. We can use the Find function to find the index of the first item greater than 10: function FindGreaterThanTen(array)
{
return Find(array, function (x) { return x > 10; });
}
But what if you want to find the first item greater than an arbitrary number? Remarkably, this works fine: function FindGreaterThan(array, n)
{
return Find(array, function (x) { return x > n; });
}
This demonstrates the true power of anonymous functions. The definition of an anonymous function has access to any and every variable that was visible where the function was defined, including local variables. Furthermore, it retains access to those variables as long as the anonymous function lives, even after the calling function has returned! An anonymous function used in this way is called a “closure.”
Whenever you create an anonymous function that is stored in such a way that it will outlive the function call that created it (which is not the case in the example above) you need to keep in mind that every local variable of that function call will live as long as the anonymous function lives. (That’s right – every local variable, not just the variables that the anonymous function happens to use.) If any of the local variables are references to objects that aren’t needed by the anonymous function, this could prevent garbage collection from freeing otherwise unused memory; if the objects reference large amounts of memory, this can quickly become a serious performance problem. If you’re working with Internet Explorer, you should also be aware of a serious memory leak that occurs when reference cycles are created that involve DOM nodes; these cycles are easy to create inadvertently when using anonymous functions as event handlers.
Occasionally, you’ll want an anonymous function to support recursion, in which case you can use arguments.callee to reference the otherwise unnamed function. var fnClearAll = function (obj)
{
for (var key in obj)
arguments.callee(obj[key]);
Clear(obj);
};
Now that I’ve talked about anonymous functions, I can describe my favorite way to enumerate an array – see my next post for a description of the ForEach function. |
3/28/2005 12:42:10 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Friday, March 18, 2005 |
|
|
Normally you access function arguments by name. function WriteAll(a, b)
{
Write(a);
Write(b);
}
However, you can also access function arguments by index using the special arguments object. This technique is particularly useful when implementing a function that takes an arbitrary number of arguments. function WriteAll()
{
for (var n = 0; n < arguments.length; n++)
Write(arguments[n]);
}
The arguments object has a length property and one integer property for each argument (starting with 0, of course), but its similarity to Array ends there – it has none of the other methods of an Array. It is easy enough to convert an arguments object to an array; I use SliceArguments: function SliceArguments(args, nStart, nEnd)
{
if (nStart === undefined)
nStart = 0;
if (nEnd === undefined)
nEnd = args.length;
var aResult = [];
while (nStart < nEnd)
aResult.push(args[nStart++]);
return aResult;
}
The arguments object has another useful property; the callee property returns the Function object that is being executed. This is primarily useful for recursion in anonymous functions, since there’s no function name to use. (I’ll talk more about anonymous functions in a future post.)
The arguments object has one more property: caller. It returns the Function object that called the currently executing function. However, the caller property has limited use, and has been deprecated in the most recent versions of JavaScript.
One more item of interest – each Function object has a length property that indicates the number of named arguments of that function. function Blorb(a, b, c) {}
// Blorb.length == 3 |
3/18/2005 12:58:31 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Tuesday, March 15, 2005 |
|
|
The nicest way to enumerate an array would be to use the for...in operator: for (var obj in array)
...
Don’t do it. The biggest surprise to the uninitiated is that this enumerates the indexes into the array, not the elements of the array itself. You’ll also find that the enumerated indexes are strings rather than integers. Worst of all, you’ll find that the indexes aren’t enumerated in numerical order, but in arbitrary order. You’ve probably figured out by now that what you’re actually doing is enumerating the properties of the array object, which happen to include the properties whose names are non-negative integers, but would also include any other custom properties you had assigned to the array.
So, how to enumerate an array? Well, the best approach is the most obvious – walk through the indexes of the array with a simple for loop: for (var n = 0; n < array.length; n++)
DoSomething(array[n]);
For a small speed increase, pull the length out of the loop. var len = array.length;
for (var n = 0; n < len; n++)
DoSomething(array[n]);
However, my favorite way to enumerate an array is to call a ForEach function that does all this for me – more on that in a future post... |
3/15/2005 9:05:23 AM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Friday, March 04, 2005 |
|
|
Arrays in JavaScript are really just Objects with properties whose names happen to be non-negative integers. They also have one very special property: length.
A new array has a length of zero. var a = []; // a.length == 0
Whenever a property whose name happens to be a non-negative integer is set, the length is automatically set to one more than that integer (unless the length is already greater than that integer). a[3] = true; // a.length == 4
a[9] = false; // a.length == 10
a[6] = false; // a.length == 10
JavaScript Arrays are “sparse” – no space is allocated for unused elements. // a[2] === undefined, (2 in a) === false
Deleting an item in the array has no effect on the length. delete a[9]; // a.length == 10
Explicitly enlarging the length of the array has no effect on the items in the array. a.length = 12; // a[11] === undefined, (11 in a) === false
Explicitly reducing the length of the array automatically deletes any items in the array whose index is greater than or equal to the length. a.length = 5; // a[6] === undefined, (6 in a) === false
You can use the length to add an item to an array (but prefer the push method). a[a.length] = true; // a[5] === true, a.length == 6
Arrays support a number of useful methods that treat the object like a traditional array:
push, pop: Adds or removes items from the end of the array.
unshift, shift: Adds or removes items from the beginning of the array.
slice: Returns a range of array items as a new array.
splice: Inserts and/or removes items to/from the array.
concat: Combines one or more arrays to form a new, concatenated array.
join: Joins the string forms of the array items into one string.
sort: Sorts the items in the array.
reverse: Reverses the items in the array.
There’s no clone method, but you can clone an array by calling either slice or concat with no arguments. |
3/4/2005 3:08:09 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Thursday, March 03, 2005 |
|
|
Since new object properties can be created at will, a plain old Object is quite useful in JavaScript.
One common use of an Object is as a simple data structure. For example, if you want to return multiple values from a function, you can return an object with multiple properties: function SplitAt(str, n)
{
var obj = new Object;
obj.Before = str.substring(0, n);
obj.After = str.substring(n + 1);
return obj;
}
Or, using the abbreviated syntax: function SplitAt(str, n)
{
return { Before : str.substring(0, n), After : str.substring(n + 1) };
}
Another common use of an Object is as an associative array; specifically, a mapping from strings to objects. var map = {};
map["George Washington"] = 1789;
map["John Adams"] = 1797;
Or: var map = { "George Washington" : 1789, "John Adams" : 1797 };
To see if a string is represented by an associative array, you could simply check that it isn’t undefined (e.g., map["Ed Ball"] !== undefined), but a property can be explicitly set to undefined, so you should use the in statement (e.g., "Ed Ball" in map).
To remove a string from an associative array, use delete (e.g., delete map["Ed Ball"]).
To see all of the strings represented by an associative array, use for...in: for (var str in map)
write(str + " (" + map[str] + ")");
When using an Object as an associative array, make sure that your strings will never be the same as the name of a built-in property of Object, e.g., "toString" or "prototype". Why? Well, for example, unless it has been overwritten, map["toString"] evaluates to a Function object instead of undefined. Furthermore, "toString" in map will always return true, but for...in will never enumerate "toString", and delete map["toString"] will always fail.
Also keep in mind that future versions of JavaScript could add new built-in properties; for example, version 5.5 added "hasOwnProperty". When I want to make absolutely sure that I won’t have any conflicts with built-in properties, I prefix the string with a space character – no built-in property will ever start with a space, after all. function Get(map, str) { return map[" " + str]; }
function Set(map, str, obj) { map[" " + str] = obj; }
function Has(map, str) { return (" " + str) in map; }
function Erase(map, str) { delete map[" " + str]; }
function Find(map, obj)
{
for (var str in map)
if (map[str] == obj)
return str.substring(1); // remove space
} |
3/3/2005 1:23:58 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Tuesday, March 01, 2005 |
|
|
JavaScript: The Definitive Guide, by David Flanagan, is an outstanding book about the JavaScript language. It has excellent documentation on both the “core” JavaScript language and “client-side” JavaScript as used by a web browser. Best of all, it doesn’t mix the two, which is particularly useful if you find yourself using JavaScript outside of a browser. It also gives the author a chance to dig deep into the core language, which is a far more powerful and expressive language than most people realize.
The second half of the book consists of three references – one for “core” JavaScript, one for “client-side” JavaScript, and one for the W3C DOM. The references aren’t nearly as useful as the first half of the book, since that sort of documentation is more widely available, but it could come in handy.
I have extensive experience using JavaScript both inside and outside of a browser, and have recently been studying the more “advanced” features. I was surprised by some of the details I had overlooked:
- The switch statement uses identity (
===), not equality (==), when looking for a matching case statement.
- Each
case of a switch statement can use any expression (not just numbers or strings).
- The first expression of a
for...in statement is evaluated for each item. (I don’t plan on taking advantage of that obscure feature.)
- You can
break or continue to a label.
FunctionName.length is the declared number of function arguments.
- If a function used as a constructor returns an object, that object becomes the “new” object.
- The
push method of an Array can take multiple arguments.
- In client-side JavaScript, “global” variables become properties of the global object, the window.
SCRIPT elements should use DEFER if they don’t call document.write.
- The Internet Explorer event model has important differences from the standard event model.
- The standard
Error object simply takes a string in its constructor (in Internet Explorer, the Error constructor is documented as taking a number followed by the message; I have since discovered that the standard behavior is supported as well).
- The
substr method of String is deprecated in favor of substring or slice. (Unfortunately, I find substr far more natural than substring or slice.)
- There are a lot of methods that only work in Internet Explorer (and this book only scratched the surface).
The book also talks some about “Object-Oriented JavaScript,” which I hope to talk more about in a future post.
If you are a JavaScript programmer but realize that you may only be scratching the surface of what is possible, or wonder why the language doesn’t always behave as you’d expect it to, I highly recommend that you read this book. |
3/1/2005 1:36:27 PM (Pacific Standard Time, UTC-08:00) | | Books | JavaScript
|
|
|
|
Friday, February 18, 2005 |
|
|
In JavaScript, the “Boolean” operators && and || aren’t very Boolean at all. In most C-style languages, a || b returns a Boolean – true if either a or b are “true,” and false otherwise. (Remember that lots of things are “true” in JavaScript – in fact, the only things that are “false” are the false Boolean, the numbers 0 and NaN, the empty string, null, and undefined.)
Actually, that’s a somewhat simplified view, because the && and || operators of C-style languages short-circuit, meaning that b is not evaluated if a is “false.” Thus it is safe to have an expression like a == null || a.isEmpty() – the isEmpty method will not be called if a is null.
Where was I? Ah, yes, in most C-style languages, a || b returns a Boolean, so it’s the same as a ? true : (b ? true : false)
But in JavaScript, a || b doesn’t necessarily return a Boolean, because it is really the same as a ? a : b
How does this work? Well, if a is “true,” then a || b returns a, which is “true.” If a is “false” but b is “true,” then a || b returns b, which is “true.” If both a and b are “false”, then a || b returns b, which is “false.”
The danger is in assuming that a || b returns a Boolean when a and b are not Booleans themselves. For example: var hasItems = a && a.getCount();
if (hasItems == true) // should be: if (hasItems)
...
Now, you shouldn’t be comparing Booleans to true or false anyway, but if you did, you’d find that hasItems equals true only when a.getCount() returns 1 (and then only because 1 equals true in JavaScript). Furthermore, hasItems equals false only when a.getCount() returns 0 (because 0 equals false). If a is null, hasItems will be null, which equals neither true nor false. If a.getCount() returns 2, hasItems equals 2, which, again, equals neither true nor false.
Be aware that the MSDN documentation is stunningly inaccurate here; it suggests that && and || always return true or false. (The JScript.NET documentation is more accurate, thankfully.)
Why does JavaScript evaluates the binary Boolean operators this way? Well, a || b provides a great shortcut for a ? a : b that only evaluates a once. Similarly, a && b provides a nice shortcut for a ? b : a. For example, instead of var pane = null;
if (view != null)
pane = view.getPane();
or var pane = view == null ? null : view.getPane();
you can use var pane = view && view.getPane();
You could argue that it’s not as clear, of course, particularly to anyone that doesn’t know about this feature of JavaScript, so take advantage of it at your own discretion.
I should also mention the unary Boolean operator of JavaScript. The “not” operator (!) is quite Boolean, because it always returns true or false. That is, !a evaluates to true if a is false, 0, NaN, "", null, or undefined; otherwise, !a evaluates to false. To explicitly convert a value to a Boolean, you could use a ? true : false, or you can simply use a double-not: !!a. |
2/18/2005 4:45:39 PM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Wednesday, February 16, 2005 |
|
|
The this statement in JavaScript is important, particularly when writing “object oriented” code. What this is depends on when you ask.
Outside of a function call, this is the global object. If your code is being hosted by a browser, this is the window object. Other script hosts provide their own global object.
Inside of a function call, what this is depends on how the function was called. A function can be called in one of four ways:
F(arg1, arg2)
obj.F(arg1, arg2)
F.call(obj, arg1, arg2)
F.apply(obj, args)
In the first case, any reference to this in the function definition will evaluate to the global object. In the second case, references to this evaluate to the specified object (obj), which is why this is so important to “object oriented” JavaScript code. In the third and fourth cases, references to this also evaluate to the object, unless obj is set to undefined, in which case this will evaluate to the global object, as with the first case.
It’s interesting to consider the relative speed of each of the four ways to call a function. Unsurprisingly, the last is generally the slowest. Quite surprisingly, to me at least, under Internet Explorer, the second case is faster than the third. That’s right – calling a function property of a global object is faster than calling a global function. Only the Great Implementor would know for sure, but it looks like the extra time in the first case is spent determining what this should be. In fact, the third case is also faster than the first case when the first argument to call is anything but undefined. (Thus, when performance is an issue, and I’ve got a function variable, I call it with the call method and specify an explicit this.)
Update: I forgot to mention another useful use of this. When an HTML element raises an event, the event handler function can use this to access the element to which the event handler was attached. window.event.srcElement provides access to the element that raised the event. There's no difference between the two unless the event has bubbled to the element. For example, if you handle onclick in document.body, window.event.srcElement will be the actual descendant element that was clicked, but this will always be document.body. |
2/16/2005 9:23:08 AM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
Tuesday, February 15, 2005 |
|
|
What is true in JavaScript? It depends on how you ask.
X === true is true only if and only if X is a Boolean variable set to true.
(What, you’ve never heard of the “triple-equals” operator? It’s actually called the “identity” operator, and X === Y when typeof(X) == typeof(Y) && X == Y. There’s also X !== Y, which is the same as !(X === Y). You’re probably familiar with the typeof operator – it returns “number”, “string”, “boolean”, “object”, “function”, or “undefined”. Notice that there’s no “integer” or “real”, so it is unsurprising (but dangerous, given the unpredictability of floating-point math) that 1 + 1 === 0.5 + 1.5. Anyway, back to what is true…)
X ? true : false is true most of the time. It’s only false when X is the number 0, or the number NaN, or the empty string, or the false Boolean, or the null object, or undefined. (This is the same logic used by the if and while statements.)
Now, equality gets really confusing. X == true whenever X can be directly converted to the Boolean true. The following meet that criteria: the true Boolean, the number 1, and strings that convert to the number 1, such as “1”, “1.0”, “1E0”, “0.1E1”, “ +1.0 ”, etc. Note that leading and trailing whitespace is ignored! Surprisingly, strings like “true” or “True” do not meet the criteria – so "1" == true, but "true" != true.
In the case of equality, we must also talk about what is false. X == false whenever X can be directly converted to the Boolean false: the false Boolean, the number 0, and strings that convert to the number 0, such as “0”, “0.0”, “ -0E1”, etc. But wait; there’s more! The empty string (“”) is also “equal to” false, as well as any string that contains only whitespace! So " \n\t\r\u2002" == false! Who knew? However, a few things that you’d think might be “equal to” false aren’t: "false" != false, NaN != false, null != false, and undefined != false.
What's particularly confusing is considering which of the following similar statements are true for any given X:
X == true
X != false
X ? true : false
If X is "1", they are all true. If X is "", none are true. But if X is null, only the second is true. If X is " ", only the last is true. And if X is "2", the last two are true. The funny thing is that it really does work as you’d expect, most of the time. The subtleties of JavaScript…
Disclaimer: My experience is actually with Microsoft’s JScript, particularly as it is implemented inside Internet Explorer. I presume that this is all true for all JavaScript engines, but I haven’t actually tested it.
Update: I replaced the second to last paragraph with something that is hopefully more interesting and more accurate. (Thanks, Bradley!) |
2/15/2005 9:16:02 AM (Pacific Standard Time, UTC-08:00) | | Code | JavaScript
|
|
|
|
|
|
|
| Archive |
| March, 2008 (1) |
| January, 2008 (1) |
| September, 2007 (1) |
| July, 2007 (1) |
| June, 2007 (3) |
| May, 2007 (2) |
| January, 2007 (2) |
| December, 2006 (1) |
| November, 2006 (2) |
| August, 2006 (6) |
| July, 2006 (3) |
| May, 2006 (4) |
| April, 2006 (2) |
| March, 2006 (1) |
| February, 2006 (1) |
| January, 2006 (1) |
| December, 2005 (2) |
| November, 2005 (5) |
| October, 2005 (11) |
| September, 2005 (7) |
| August, 2005 (1) |
| July, 2005 (2) |
| June, 2005 (2) |
| April, 2005 (5) |
| March, 2005 (11) |
| February, 2005 (6) |
| January, 2005 (2) |
| December, 2004 (1) |
| November, 2004 (1) |
| October, 2004 (3) |
| September, 2004 (1) |
| August, 2004 (5) |
| July, 2004 (10) |
| June, 2004 (3) |
| May, 2004 (5) |
| April, 2004 (3) |
| March, 2004 (10) |
| February, 2004 (10) |
| January, 2004 (15) |
| December, 2003 (1) |
| November, 2003 (2) |
| October, 2003 (13) |
| September, 2003 (20) |
| August, 2003 (24) |
|
|
|
|