By Kris Kowal | Published: August 27, 2007 - 03:48PM CT
Novice: objects and associative arrays
foo = Object(); foo = ;
Also, item indexing and member selection are equivalent.
foo.a == foo;
foo1 == foo(1).toString() == foo;
Novice: cute type conversions
You can convert any object to a number by adding it to zero.
0 + foo
You can convert any object to a string by adding it to a null string.
However, strings and numbers are special. If you add a string and a number, the resultant type depends on whether the string is empty. An empty string gets coerced to the number zero. Otherwise, the zero gets coerced to a string and the addition performs concatenation.
0 + == 0 + 0 == 0 1 + == 1 + 1 == 1 0 + == + 0 ==
You can convert any object to a boolean, true or false value by negating it twice.
( index items) item = itemsindex; ...
( i = 0; i < items.length; i++) item = itemsi; ...
Of course, these libraries and new browsers also provide alternate iteration mechanisms that are arguably more elegant. With recent versions of Firefox and libraries like Prototype, you get forEach iteration, which involves an anonymous function.
items.forEach( (item) ... );
Acolyte: anonymous functions
range = (start, stop, step) () at = start; start += step; (at < stop) at; Error(); ; ; next = range(0, 10, 2);
The first call of next returns 0. The following times you call next it returns 2, 4, 6, and 8. Thereafter, calling next will throw an Error. This is because the local variables of range, the one time you've called it, remain in memory as long as next exists. The anonymous function that range returns is a closure: both the code of the function and a reference to the scope in which the function was declared. The returned function object effectively holds the range "function allocation record" in memory. Thus, the value of start persists across calls to next.
( () ... ).call();
Another handy use for enclosures is to make using with and var behave deterministically and intuitively. The with block pushes an Object onto the scope chain. That is, members of a given object become local variables in the given block.
(: 10) a == 10;
However, when you declare variables with var inside a with block, the variables should be declared in the function block scope and a variable by the same name should be initialized in the with block scope. However, all bets are off depending on what browser runs the script. Using an enclosure inside all with blocks will force var declarations to operate in the innermost scope.
(: 10) () a = 20; ).call();
Acolyte: context object manipulation
bar = () ; bar() == ;
There are two ways to send other objects as the context object into a function. The first is to call a function as a member of an object.
foo = : bar; foo.bar() == foo;
The second way to send an object to a function as its context object is explicit. You can use the Function.call method's first argument to send in an arbitrary object. call accepts the context object followed by any arguments you want to pass in normally.
bar.call(foo, 0, 1, 2, ...);
Acolyte: variadic arguments
Consider a trivial max function that returns the largest of two numbers.
.max = (a, b) (a > b) a; b; ;
max can use the arguments to read any number of arguments.
.max = () items = ; basis; ( member items) item = itemsmember; (basis === || item > basis) basis = item; ; basis; ;
If you want to pass variadic arguments to a function from an arbitrary number of items in an Array, you can use the Function.apply function. Like call, apply accepts the context object as its first argument, followed by a single Array containing whatever arguments to you want to pass.
max.apply(, 10, 30, 20) == 30;
You've likely noticed that the context object a function receives depends on the invocation syntax. Binding a function to an object assures that you receive a particular object. Here's a sample bind function.
bind = (context, functor) () functor.apply(context, ); ; ;
Guru: lazy function definition
Peter Michaux recently documented and named the "Lazy Function Definition Pattern" wherein you determine the behavior of a function the first time the user calls it. This is handy for choosing among cross-platform code paths. Consider this function that gets the value of PI by calculating it once, then simply returning it after each successive call.
getPi = () pi = calculatePi(); getPi = () pi; pi; ;
Guru: polymorphic callable objects
Supposing you were writing a type system, you might want objects of all your types to have the option of being callable, and for the call behavior to be redefinable in sub-types. One fun way to do this is to always use Function instead of Object as your base type.
callableType = (constructor) () callableInstance = () callableInstance.callOverload.apply(callableInstance, ); ; constructor.apply(callableInstance, ); callableInstance; ; ;