Information Technology
ENContact

Functions

1 Basics

1.1 Definition and Invocation

We define a function add that adds two numbers and returns the result.


function add(x, y) { // function definition 
    var z = x + y;
    return z;
}
// Test:
var sum = add(1, 2); // function call (invocation)
document.write("sum = " + sum);
Output:


add is the name of the function, x, y are the arguments, and the code between { } is the body.

1.2 Argument Passing and Scoping

The function body introduces a new scope block. The code inside the body has access to the outside block. The variables defined inside the body are unknown outside the body.

The argument variables are passed by value.


var offset = 5;
function addOffset(x) {
    var offset2 = 2 * offset;  // (1) define local variable, access global variable
    x += offset;               // (2) change argument
    return x;
}
// Test:
var x1 = 1;
var shift1 = addOffset(x1); 
document.writeln("shift1 = " + shift1);
if (typeof(offset2) == "undefined") { // (1) local vars unknown outside   
    document.writeln("offset2 undefined "); 
}
if (! this.offset2) { // just another interesting test   
    document.writeln("offset2 undefined "); 
}
document.writeln("x1 = " + x1); // (2) the passed argument was not changed
var shift2 = addOffset(1 + 2);     // the value 3 is passed
document.write("shift2 = " + shift2); 
Output:


2 Functions as Objects

2.1 Function Properties

Functions are objects and have properties.


function add(x, y) {
    z = x + y;
    return z;
}
// Properties of the 'add' object:
document.writeln("add.toString() = " + add.toString());
document.writeln("add.name   = " + add.name); // name is non-standard
document.writeln("add.length = " + add.length);
document.writeln("add.prototype = " + add.prototype);

var sum = add; // sum is the same function object as add
var s = sum(1, 2);
document.write("s = " + s);
Output:


2.2 Arguments

The properties arguments and callee exist inside the function body.


function add(x, y) {
    document.writeln("Arguments:");
    for (var i = 0; i < arguments.length; ++i) {
        document.writeln(i + ": " + arguments[i]);
    }
    document.writeln("This function:");
    document.write(arguments.callee.toString()); // callee is this function
    var z = x + y;
    return z;
}
var sum = add(10, 11);
Output:


A function can be called with less arguments than there are defined. The arguments that are not supplied are 'undefined' inside the body.

A function can be called with more arguments than there are defined. The surplus arguments are passed to the function and are available via the arguments array inside the body. They are obviously not accessible by argument names.


function add(x, y) {
    document.writeln("Arguments:");
    // Write supplied arguments:
    for (var i = 0; i < arguments.length; ++i) {
        document.writeln(i + ": " + arguments[i]);
    }
    document.writeln("y = " + y);
    var z = x + y;
    return z;
}

document.writeln("Supply less arguments:");
var sum = add(10);
document.writeln("Sum = " + sum + "\n");

document.writeln("Supply more arguments:");
sum = add(1, 2, 10);
document.write("Sum = " + sum);
Output:


2.3 Function Literals

Function literals are anonymous functions.


var add = 
    function(x, y) {var z = x + y; return z;}; // function literal (anonymous function)

var sum1 = add(1, 2);
document.writeln("sum1 = " + sum1);

var sum2 = function(x, y) {return x + y;} (11, 12) // anonymous function called
document.write("sum2 = " + sum2);
Output:


2.4 Passing Functions

Functions can be passed as arguments to other functions.


function add(x, y) {return x + y;}
function mul(x, y) {return x * y;}

function calculate(f, x, y) {
    return f(x, y);
}

var sum = calculate(add, 2, 3);
var prod = calculate(mul, 2, 3);
document.writeln("sum  = " + sum);
document.write("prod = " + prod);
Output:


2.5 Returning (Computing) Functions

Functions can be returned as results of other functions.


function add(x, y) {return x + y;}
function mul(x, y) {return x * y;}

function calculate(op) {
    if (op == "+") {
        return function (x, y) {return x + y;};
    }
    else if (op == "*") {
        return function (x, y) {return x * y;};  
    }
    else {
        throw "Not supported operation";
    }
}

var sum = calculate("+") (20, 30);
var prod = calculate("*") (20, 30);
document.writeln("sum  = " + sum);
document.write("prod = " + prod);
Output:


2.6 Adding Properties to Functions

Properties can be added to functions. This supports the locality principle.


function transform(x, y) {
    return transform.scale * x + y;
}
transform.scale = 10;

var z = transform(1, 2);
document.write("z = " + z);
Output:


3 Functions as Methods

3.1 Global Functions

Functions defined in the global scope can be considered properties of the global object this.


var scale = 10;
function transform(x, y) {
    var z = this.scale * x + y; // this refers to the global object
    return z;
}

var z = this.transform(1, 2); // this refers to the global object
document.write("z  = " + z);
Output:


3.2 Object Methods

Functions can be added as properties of arbitrary objects.


var scale = 10;

var transformer = { // object defined using a literal
    scale: 2,
    calculate: function(x, y) {
        var z = this.scale * x + y; // 'this' refers to this transformer object
        return z;
    }
}

// Call the method on the object transformer (the usual case):
var z1 = transformer.calculate(1, 2);
document.writeln("z1 = " + z1);

// Call the function transform in the global scope:
var transform = transformer.calculate; // get the function
var z2 = transform(1, 2); // equivalent to this.transform(1, 2) on global 'this'
document.write("z2 = " + z2); 
Output:


3.3 Call and Apply

Global functions can be called as methods of arbitrary objects using the predefined methods call and apply. If f(x1, ... xn) is a global function, then both f.call(object, x1, ... xn) and f.apply(object, [x1, ... xn]) correspond semantically to "object.f(x1, ... xn)". The reference this inside f(x1, ... xn) is bound to the respective object.


var scale = 10;

function transform(x, y) {
   var z = this.scale * x + y; // 'this' is the global object
   return z;
}

var transformer = {
    scale: 2,
    calculate: transform // 'this' inside transform is this object
}

var z1 = transform(1, 2); // equivalent to this.transform(1, 2) on global 'this'
document.writeln("z1 = " + z1);
 
var z2 = transformer.calculate(1, 2);
document.writeln("z2 = " + z2);

var z3 = transform.call(transformer, 1, 2);
document.writeln("z3 = " + z3);

var z4 = transform.apply(transformer, [1, 2]);
document.write("z4 = " + z4);
Output:


4 Nested Functions

Function definitions can be nested. As expected, locally defined functions have access to the local scope of the enclosing function. Local functions are not visible outside the enclosing function.

The Function() constructor does not use lexical scoping.


function filterArray(array, threshold) {
    var limit = threshold; // local variable
    function filter(y) {return y > limit;} // local function
    var result = [];
    for (var n = 0; n < array.length; ++n) {
        if (filter(array[n])) result.push(array[n]);
    }
    return result;
}
// Test:
document.write("Values greater than 1: ");
var a = filterArray([1, 2, 3], 1);
for (var n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}
Output:


5 Closures

5.1 Closure Concept

Nested functions keep the local state of the enclosing function. Since they "close" the state of the enclosing lexical environment to the state of their definition, they are called closures.


function makeFilter(threshold) {
    var filter = function(y) {return y > threshold;} // threshold is kept
    return filter;
}
    
function filterArray(array, filter) {
    var result = [];
    for (var n = 0; n < array.length; ++n) {
        if (filter(array[n])) result.push(array[n]);
    }
    return result;
}

// Test:
var filter1 = makeFilter(1); // filter1 keeps threshold=1
var filter2 = makeFilter(2); // filter2 keeps threshold=2
document.write("Values greater than 1: ");
var a = filterArray([1, 2, 3], filter1);
for (var n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}
document.write("\nValues greater than 2: ");
a = filterArray([1, 2, 3], filter2);
for (n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}
document.write("\nValues greater than 1: ");
a = filterArray([1, 2, 3], filter1);
for (n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}
Output:


5.2 Properties of Nested Functions

Almost the same effect as in 5.1 can be achieved by setting properties of nested functions to the fixed parameters. The difference is that these properties can be changed afterwards. On contrary, the closure state can not be changed and hence is the preferred solution.


function makeFilter(threshold) {
    var filter = function f(y) {
        return y > f.threshold; // uses own property f.threshold
    } 
    filter.threshold = threshold; // threshold is kept as property of filter
    return filter;
}
    
function filterArray(array, filter) {
    var result = [];
    for (var n = 0; n < array.length; ++n) {
        if (filter(array[n])) result.push(array[n]);
    }
    return result;
}

// Test:
var filter2 = makeFilter(2); // passes values greater than 2
document.write("Values greater than 2: ");
a = filterArray([1, 2, 3], filter2);
for (n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}

// Can change filter2:
filter2.threshold = 0;
document.write("\nValues greater than 2: ");
a = filterArray([1, 2, 3], filter2);
for (n = 0; n < a.length; ++n) {
    document.write(n == 0 ? a[n] : ", " + a[n]);
}
document.write("  ... oops");
Output:


6 Function() Constructor

6.1 Runtime Definition of Functions

A function f(x1, ... xn){body} can be defined dynamically at runtime using the Function() constructor as new Function("x1", ... "xn", "body"). The arguments are strings, the last argument is the body of the function. A dynamically defined function performs of course less efficiently than statically defined function.


var add = new Function("x", "y", "var z = x + y; return z;");
// Test:
var sum = add(2, 3);
document.write("sum = " + sum);
Output:


6.2 Scoping

The Function() constructor does not use lexical scoping.


var factor = 10; // global variable factor

function scale(x) {
    var factor = 0; // local variable factor 
    var f = new Function("y", "return factor * y;"); // uses global factor
    return f(x);
}

// Test:
var z = scale(2);
document.write("z = " + z);
Output: