Understanding JavaScript functions
When writing functions, you need to understand that the function
signature consists of the function name, parameters, and scope. In C#
programming against the SharePoint server-side API, the calling code
should match the function signature by passing in parameters that are
typed appropriately. Furthermore, an error is thrown when the calling
code does not match the function signature. In JavaScript, however, no
error is thrown when the list of parameters passed to a function does
not match the function signature. Instead, all parameters are available
within a function through the arguments array. Consider the following JavaScript function:
function Add(){
var sum = 0;
for (var i=0; i<arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
The Add function definition does not include any parameters. Instead, the function looks through the arguments array and simply adds together the values contained within it. Because of this, the following calls to the Add function will all succeed:
var sum1 = Add();
var sum2 = Add(7);
var sum3 = Add(7,11);
var sum4 = Add(7,11,21,36);
Functions in JavaScript are actually objects. As such, they can be
assigned to a variable. The variable referencing the function can then
be invoked as if it were the name of the function. A function can also
be defined without a name, making it an anonymous function. The following code shows an example of an anonymous function assigned to a variable named talk and then invoked:
var talk = function() {
alert("hello there!");
};
talk();
Understanding JavaScript closures
Because anonymous
functions can be assigned to a variable, they can also be returned from
other functions. Furthermore, the local variables defined within the
containing function are available through the returned anonymous
function. This concept is called a closure. Consider the following code that returns an anonymous function from a containing named function:
function echo (shoutText) {
var echoText = shoutText + " " + shoutText;
var echoReturn = function() { alert(echoText); };
return echoReturn;
}
Because the return value from the named function is an anonymous
function, the code that follows can be used to invoke the returned
function. When the returned function is invoked, the browser displays
the text “Hello! Hello!”.
echo("Hello!")();
What is interesting in this example is the fact that the anonymous function is using the local variable echoText
within its body, and the local variable is available even after the
function returns. This is possible because the returned value is
essentially a pointer to the anonymous function defined within the
named function, which means that the local variables do not go out of
scope after the named function completes. This is the essence of a
closure in JavaScript.
At first glance, closures
might appear to be more of a curiosity than a useful construct.
However, closures are essential to the process of creating encapsulated
JavaScript that is maintainable. Consider the following code:
function person (name) {
var talk = function() { alert("My name is " + name); };
return {
speak:talk
};
}
In the preceding example, an anonymous function is assigned to the local variable talk. The return value of the function is an object that has a key speak, which references the value talk.
By using this type of closure, the function can be invoked by using
method syntax, which returns the message “My name is Brian Cox”.
person("Brian Cox").speak();
Notice how the code that invokes the function appears
almost as if it is object-oriented. Even though JavaScript is clearly
not object-oriented, by using closures, you can create functions that
look and feel more familiar to C# developers and significantly improve
maintainability.
Understanding JavaScript prototypes
A JavaScript object is really just an unordered collection of
key-value pairs. Objects can be created with the key-value pairs
defined at the moment they are created. The keys are then used to
access the values. The following code shows a simple customer object with a name property defined:
customer = {Name: "Brian Cox"};
alert("My name is " + customer["Name"]);
Every JavaScript object is based on a prototype, which is an object that supports the inheritance of its properties. With prototypes, you can define the structure of an object and then use that structure to create new object instances. Example 1 shows an example of defining a prototype and creating an object from it.
Example 1. Creating an object from prototypes
var human = Object.create(null);
Object.defineProperty(human, "name",
{value: "undefined",
writable: true,
enumerable: true,
configurable: true}
);
var customer = Object.create(human);
Object.defineProperty(customer, "title",
{value: "undefined",
writable: true,
enumerable: true,
configurable: true}
);
customer["name"] = "Brian Cox";
customer["title"] = "Developer";
alert("My name is " + customer["name"]);
alert("My title is " + customer["title"]);
In Example 1, a null human prototype is created and then a single name property is defined. The human prototype is then used to create an instance called customer. The customer prototype is then modified to contain a title
property. If you call a property on an object but the property does not
exist, JavaScript will look for the property by following the prototype chain up the inheritance tree. In this case, the name property of the customer is defined in the human prototype.
Using prototypes is very efficient when you are creating
large numbers of objects because the functions do not need to be
created for each instance. This behavior results in development
patterns that are presented in the next section.