Dapeng Li

Hungry, foolish and passionate – yet another software developer.

Just Enough JavaScript – Function

leave a comment

This is the second post on Just Enough JavaScript, we’ll cover function in JavaScript.

Function

JavaScript functions are objects, so they can be treated like other objects: they can be assigned to variables, assigned to properties of an object (or a function), passed to another funciton as arguments, or returned from a function.

Declaration

  • Function can be declared in a statement. This Function Decalartion Statement syntax should be familiar.
// ex_2.1
// function_declaration_1

function f(x) {
	return x + 1;
}
  • Function can also be declared in an expression. In this Function Definition Expression way you declare an anonymous function (or with a name if it’s meant to be called recursively) and assign it to a variable.
// ex_2.2
// function_declaration_2

var f = function(x) {
	return x + 1;
}

The statement way (ex 2.1) is just a shorthand for the expression way (ex 2.2), Douglas Crockford recommends the latter because it better conveys the notion that a function value was assigned to a variable. 1

Function declared in the statement way (ex 2.1) can be invoked before or after the declaration; function declared in the expression way (ex 2.2) can only be invoked after its declaration.

Invocation Context (this) 2

To invoke a function in code, specify the function name, open parenthesis, a list of arguments and close parenthesis.

JavaScript doesn’t perform checking on type or number of arguments when invoking a function, additional arguments will be ignored, and undefined will be placed for missing arguments.

There are four patterns for invoking a function, which will determine what this keyword means inside the function.

Method Invocation Pattern

When a function is stored as a property of an object, it is called a method. When a method is invoked (using . notation or [] expression), this is bound to that object.

// ex_2.3
// method_invocation_pattern

var calculator = {  // An object literal
	operand1: 1,
	operand2: 1,

	add: function () {
		// Note the use of the this keyword to refer to this object.
		this.result = this.operand1 + this.operand2;
	}
};

calculator.add();  // A method invocation to compute 1+1.
console.log(calculator.result);  // 2

Invoking a function on an object in this pattern conveys the intent that the method operates on the object.

Function Invocation Pattern

When a function is not the property of an object, then it is invoked as a function. When a function is invoked with this pattern, this is bound to the global object. As Douglas Crockford said, it was a mistake of the language design – this should bound to the this variable of the outer function.

// ex_2.4
// function_invocation_pattern

var foo, obj;

foo = function () {
	this.bar = 123;
};

foo();  // invoke using Function Invocation Pattern, this keyword bounds to global

console.log(bar);  // 123

obj = {
	bar: "Inside obj",
	my_foo: foo
};

obj.my_foo();  // invoke using Method Invocation Pattern, this keyword bounds to obj

console.log(obj.bar);  // 123

Be careful when invoking methods in an object, when you invoke other functions defined in the object from a method, this keyword will not bound to the same object (instead, this bounds to global). A workaround is to store the object context as a local variable, and use that variable instead of this. It’s better explained in an example:

// ex_2.5
// method_invocation_pattern2

var o = {  // An object o.
	m: function () {  // Method m of the object.
		var self = this;  // Save the this value in a variable.

		console.log(this === o);  // Prints "true": this is the object o.

		f();  // Now call the helper function f().

		function f() {  // A nested function f
			console.log(this === o);  // "false": this is global or undefined
			console.log(self === o);  // "true": self is the outer this value.
		}
	}
};

o.m();  // Invoke the method m on the object o.

Constructor Invocation Pattern

When a function is intended to be used for initializing new objects, it’s called constructor. Constructors should be called with the new keyword, when a constructor is invoked with the new keyword, this keyword inside the constructor is bound to the newly created object (which will be initialized by the code in the constructor).

The bad news is that there’s no difference between a function and a constructor from syntax perspective, if a constructor was invoked without the new keyword, bad things can happen – properties which were meant to be inside an object were attached to global. So it’s a convention to capitalize the first letter of the name of constructor.

// ex_2.6
// constructor_invocation_pattern

var MyType, myObj;

MyType = function () {  // MyType is a constructor
	this.value = "foo";
};

myObj = new MyType();  // invoke using Constructor Invocation Pattern, this keyword bounds to myObj

myObj.get_foo = function () {  // assign a function to a property of myObj
	return this.value;
};

console.log(myObj.get_foo());  // foo

We’ll revisit constructor in our next post on Prototype and Inheritance.

Apply Invocation Pattern (Indirect Invocation)

You can specify this by invoking the call() or apply() methods on a function.

The first argument passed to call() or apply() will be the context when invoking the function, and becomes the this keyword inside the function.

Additional arguments passed to call() after the first invocation context argument will be passed to the function that is invoked.

apply() method accepts the second argument as an array.

// ex_2.7
// apply_invocation_pattern

var MyType, myObj, anotherObj, result, result2;

MyType = function () {
	this.value = "foo";
};

myObj = new MyType();  // create a new object using MyType as constructor

anotherObj = { value: "bar" };  // create another object using object literal

myObj.appendToValue = function (suffix) {  // assign a function to a property of myObj
	return this.value + suffix;
};

result = myObj.appendToValue.call(anotherObj, "beque");  // call takes additional arguments for the method to invoke

result2 = myObj.appendToValue.apply(anotherObj, ["beque"]);  // apply takes the second argument as an array

console.log(result);  // barbeque

console.log(result2);  // barbeque

Namespace

All JavaScript objects not inside a function are bound to the global context, so it’s a common practice to create a function “wraps” your code, acting like a namespace. You need to invoke that wrapper function after declaration.

// ex_2.8
// namespace

var myModule = function () {
	// Module code goes here.
	// Any variables used by the module are local to this function
	// instead of cluttering up the global namespace.
};

myModule(); // But don't forget to invoke the function!

It’s more idiomatic to declare an anonymous function and invoke it immediately after declaration.

// ex_2.9
// namespace_IIFE

(function () {  // Create an IIFE (Immediately-Invoked Function Expression) as namespace
	// Module code goes here.
}());

window.foo = (function () {  // Attach another IIFE to global, code in other files can access it
	// Code for another module
}());

There’s a name for the immediately invoked function: IIFE (Immediately-Invoked Function Expression). To know more about IIFE – the name, the syntax (why parentheses around anonymous function are needed, why you should still use parentheses around anonymous function when assigning IIFE to a variable), please read this post on IIFE from Ben Alman.

You can attach your IIFE to global object (window or global) so code in other files can access your code in IIFE, as we did in ex 2.9.

Closure

Concept

Let’s revisit the concept of lexical scoping (introduced in Variable Scope section from last post): functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked.

Also, a variable is visible throughout the function in which it’s declared, and also within any functions nested within that function.

Since functions are objects, function object can have properties which are functions. Consider this example:

// ex_2.10
// closure

var getValue = (function () {
	var value = "foo";

	return function () {
		return value;  // this function has access to variable value of outer function when it's declared
	};
}());  // IIFE (Immediately-Invoked Function Expression)

console.log(getValue());  // foo?

On line 4, an anonymous function is invoked immediately after it’s declared, the return value is a function, and assigned to an object named getValue.

On line 12, the anonymous function on line 4 had returned so local variables in that anonymous function should be out of scope. However, getValue object points to a function which has access to the local variable value when that function was declared (line 7, because nested function has access to local variables declared in parent functions). What will happen if the function stored in getValue got invoked?

Here comes the concept of closure: it’s the combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved.

In our example, when the function on line 7 was declared it had variable value in scope, so the variable value will always in scope to that function. It’s as if function “closed over” all variables in scope when it was declared.

So the function value stored in variable getValue has access to the variable value when it’s invoked. The output of last example is “foo”.

If you’re confused about the concept of closure, I recommend reading the closure section of the book JavaScript: The Definitive Guide 3, it’s very well explained there and with some good examples.

Encapsulation

Closures can be used for encapsulation, as in this example:

// ex_2.11
// closure_encapsulation

var counter, c, d;

counter = function () {
	var n = 0;

	return {  // returns an object literal encapsulating access to variable n
		count: function () { return n++; },
		reset: function () { n = 0; }
	};
};

// Create two counters
c = counter();
d = counter();

c.count();  // => 0
d.count();  // => 0: they count independently

c.reset();  // reset() and count() methods share state

c.count();  // => 0: because we reset c
d.count();  // => 1: d was not reset

The returned object literal has two functions count and reset, those two functions are the only way to access variable n.

Another thing to note from last example is that object c and d has inner states independent from each other.

Closure with locked variables

Be careful when returning functions from a loop:

// ex_2.12
// closure_lost_context

var constructFunctions, functions;

// Return an array of functions that return the values 0-9
constructFunctions = function () {
	var funcs = [], i;

	for (i = 0; i < 10; i++) {
		funcs[i] = function () {
			return i;  // encapsulate i in each function created
		};
	}

	return funcs;
};

functions = constructFunctions();
console.log(functions[5]());  // What does this return?

In the example above, each function in functions array has access to variable i, invoking each function will cause the value of i to be returned. However, when the funcs array is returned, the for loop had ended, the value of i is 10, so each closure created in funcs array will return the “current” value of i, which 10.

A workaround is to lock the variables in each iteration in a closure:

// ex_2.13
// closure_lock

var constructFunctions, functions, i;

// Return an array of functions that return the values 0-9
constructFunctions = function () {
	var funcs = [], returnValueFunc;

	returnValueFunc = function (value) {
		return function () {
			return value;
		};
	};

	for (i = 0; i < 10; i++) {
		funcs[i] = returnValueFunc(i);
	}

	return funcs;
};

functions = constructFunctions();
console.log(functions[5]());  // 5

Or use an anonymous function instead of named function as closure:

// ex_2.14
// closure_lock2

var constructFunctions, functions, i;

// Return an array of functions that return the values 0-9
constructFunctions = function () {
	var funcs = [];

	for (i = 0; i < 10; i++) {
		funcs[i] = (function (value) {  // wrap the function in an IIFE, lock the variable i
			return function () {
				return value;
			};
		}(i));
	}

	return funcs;
};

functions = constructFunctions();
console.log(functions[5]());  // 5

You can also lock the variables immediately with a closure when entering a loop:

// ex_2.15
// closure_lock3

var constructFunctions, functions, i;

// Return an array of functions that return the values 0-9
constructFunctions = function () {
	var funcs = [];

	for (i = 0; i < 10; i++) {
		(function (lockedIndex) {  // wrap code of each iteration in a closure, lock variables
			funcs[lockedIndex] = function () {
				return lockedIndex;
			};
		}(i));
	}

	return funcs;
};

functions = constructFunctions();
console.log(functions[5]());  // 5

I read about this trick from the IIFE post above, personally I feel it’s more readable this way.

Functional Programming 4

Because functions are objects in JavaScript, we can manipulate functions with functions.

ForEach

In the following example, an array and a function action() are passed as arguments to a function forEach(), the action() function will be invoked iteratively, each item in the array as argument is passed to action as argument.

// ex_2.16
// functional_foreach

var print, forEach, i;

print = function (value) {
	console.log(value);
};

forEach = function (array, action) {  // accepts second argument as a function
	for (i = 0; i < array.length; i++) {
		action(array[i]);
	}
};

forEach(["a", "b", "c"], print);

We can create other helper functions using the forEach() function in last example, here’s a function returns the sum of all items in an array (assuming all items are numbers):

// ex_2.17
// functional_sum

var forEach, i, sum, result;

forEach = function (array, action) {
	for (i = 0; i < array.length; i++) {
		action(array[i]);
	}
};

sum = function (numbers) {
	var total = 0;

	forEach(numbers, function (item) {
		total += item;
	});

	return total;
};

result = sum([1, 2, 3]);

console.log(result);  // 6

Map

Let’s try creating another helper function called map(), this function will accept an array, and an action will be performed on each item in the array, result after each processing will be pushed into a new array, finally the new array will be returned. It’s much simpler to express it in code:

// ex_2.18
// functional_map

var map, i, doubled;

map = function (array, action) {
	var newArray = [];

	for (i = 0; i < array.length; i++) {
		newArray.push(action(array[i]));
	}

	return newArray;
};

doubled = map([1, 2, 3], function (value) {
	return value * 2;
});

console.log(doubled);  // [2, 4, 6]

Reduce

Let’s create a last helper function called reduce(), which will accept an array, an initial value, and an action function. reduce() will process each item in the array with the action function, and return the accumulate result of each processing.

// ex_2.19
// functional_reduce

var reduce, i, sum, multiply, result, result2;

reduce = function (array, initial, action) {
	var accumulated = initial;

	for (i = 0; i < array.length; i++) {
		accumulated = action(accumulated, array[i]);
	}

	return accumulated;
};

sum = function (numbers) {
	return reduce(numbers, 0, function (accu, item) {
		return accu + item;
	});
};

multiply = function (numbers) {
	return reduce(numbers, 1, function (accu, item) {
		return accu * item;
	});
};

result = sum([1, 2, 3, 4]);  // 10
result2 = multiply([1, 2, 3, 4]);  // 24

console.log(result);
console.log(result2);

This concludes our review of functions in JavaScript, in next post we’ll see a prototypal way of inheritance.


1. More details from page 113 in the book JavaScript: The Good Parts by Douglas Crockford, Amazon Link

2. More details from page 27 in the book JavaScript: The Good Parts by Douglas Crockford, Amazon Link

3. More details from page 180 in the book JavaScript: The Definitive Guide, 6th Edition by David Flanagan, Amazon Link

4. More details from page 71 in the book Eloquent JavaScript: A Modern Introduction to Programming by Marijn Haverbeke, Amazon Link

Written by Dapeng

May 16th, 2012 at 7:46 pm

Posted in Programming

Tagged with