Skip to content

Making Associations

In Defining Functions, we discussed functions as rules — taking an input and then applying some rules to it to produce an output. This is the way functions are most often used in mathematics.

But there is a different perspective one can take, where a mathematical function is the association of valid inputs to particular outputs. For example, we might associate the color “red” with the code #FF0000. There is no rule that can translate the string “red” into that code, the association is simply given.

In our page on functions, we used the analogy of mathematical functions as tools. The association perspective suggests a different analogy: books. As we will see, arrays are like books organized by page number, while associative arrays are like dictionaries organized by words.

In JavaScript, square brackets are used to denote the input of an association. So
color["red"] = #FF0000; assigns the code #FF0000 to the string “red” in the color variable.

Arrays

An array is a list of data. Instead of appending a set of data to a bunch of different identifiers, we can use a single array and access its entries with different index numbers.

var a = 5;
var b = 7;
var c = -1.2;
console.log(a + b + c);  // 10.8

//same result
var x = [];
x[0] = 5;
x[1] = 7;
x[2] = -1.2;
console.log(x[0] + x[1] + x[2]); // 10.8

The two chunks of code above perform the same task. And if this is all we are interested in doing, we’d be just as well off using the first version. But now let us consider something a bit bigger. Compare the first version’s approach with using an array and a for loop.

var a = 5, b = 7, c = -1.2, d = 6, e = 9, f = 3.14, g = 87,
      h = -44, i = -83.52345, j = 4;
var sum = 0;
sum = a + b + c + d + e + f + g + h + i + j;
console.log(sum.toFixed(5)); // 7.58345

var x = [5, 7, -1.2, 6, 9, 3.14, 87, -44, -83.52345, 4];
var sum = 0;
for (var i = 0; i < 10; i += 1) {
    sum += x[i];
}
console.log(sum.toFixed(5));  // 7.58345

We can create an array by enclosing a comma-separated list of values between square brackets: [data1, data2, ....]. If we assign this to a variable, as in arr = [data1, data2, ....], then we can access individual values within the array using square brackets like so: arr[2]. Here, the number 2 is called the index of the array. Since an index always starts at 0, arr[2] refers to the third value in the array.

It is possible to jump ahead in the creation of entries; that is, to create arr[9] = "far out" when the array has only 3 entries. However, you should avoid this as JavaScript fills in the missing entries, potentially leading to performance issues and errors.

Before we leave the example above, let us do one more simplification of the code by creating a sum function.

var x = [5, 7, -1.2, 6, 9, 3.14, 87, -44, -83.52345, 4];

var sumFun = function(arr, n) {
    //default length is array length
    n = n || arr.length;

    var sum = 0;
    for (var i = 0; i < n; i += 1) {
        sum += arr[i];
    }
    return sum;
};

console.log(sumFun([5, 7, -1.2], 3)); // 10.8
console.log(sumFun(x).toFixed(5)); // -7.58345
console.log(sumFun(x, 2)); // 12

Observe how compact and powerful it is to define a function that can be used on any array. The expression arr.length will yield the number of items currently in the array arr. Also notice how with the use of var, the sum and i variables inside the function do not impact anything in the global space. This is good for variables that are not of primary concern.

Arrays can contain any kind of data from numbers to strings, to other arrays, to functions and objects. When accessing a compound structure such as an array within an array or an array containing a function, the first square bracket applies to the outer array and then the access notation that follows applies to what is returned from the outer array. Observe:

//multiple data types including array
outer = [[7, 9], function(name) {
    return "hi " + name;}
];
console.log(outer); // 7,9,function (name) {return "hi "+name;
console.log(outer[0]); // 7,9
console.log(outer[1]); // 7,9,function (name) {return "hi "+name; 

console.log(outer[0][1]); // 9
console.log(outer[1]("jack")); // hi jack

Stacks

We said above that one should not add new entries far away in index from the current entries. How do we do that nicely? The simple notation, one you should use often, is arr.push(element) for an already defined array arr. To take the last element off the array we use arr.pop() and we end up with the last entry with the array shortened by 1. And to go through the elements without removal, use a for loop as above.

//initialize array
var stack = ["a", "b"];
//add the number 3 to the end
stack.push(3);
console.log(stack); //a,b,3
console.log(stack.pop(), stack.pop()); //3,b
console.log(stack); //a

stack.push(5, 7, ["d", "e"]);
console.log(stack); //a,5,7,d,e

stack.push([3, 4]);
console.log(stack.pop()); //3,4
console.log(stack.pop()); //d,e

The mental image for this is a stack of things. We push one item onto this stack and then we can pop it off. This is a good way to think of arrays. A bad way to think of them is as putting addresses on data, such as street addresses or phone numbers, etc. If you want to label the data meaningfully, use objects as described below. Indices are not labels of the data, but rather of the containers. So if we use the built-in methods to sort an array, for example, the data changes around in the containers in the array because which data corresponds to which number is not relevant. Relationships to the other data, such as with sorting, may be important, but what number is assigned to the data should not be important at all.

Before moving onto objects, how could we model waiting in line? With push/pop, the last one in the line is the first one served. That is not the right behavior. To accomplish this, we can use push as before, but to remove the first item of the array, we use shift which returns first item while deleting it from the array. The method shift will shift all the data downwards so that the second item becomes the first with index 0.

//open cash register
var register = [];

//Alice comes along
register.push("Alice");

//Before Alice is done, Bob comes
register.push("Bob");
console.log("Serving: " + register); //Serving: Alice,Bob

//Alice finishes
console.log(register.shift() + " is done!"); //Alice is done!
console.log("Serving: " + register); //Serving: Bob

//Cecilia and Doug join
register.push("Cecilia", "Doug");
console.log("Serving: " + register); //Serving: Bob,Cecilia,Doug

//Bob and Cecilia finish
console.log(register.shift() + " is done!"); //Bob is done!
console.log(register.shift() + " is done!"); //Cecilia is done!

Please note in the above example that avoiding the use of indices is very appropriate and makes for more readable, elegant code.

Objects

The most general data structure in JavaScript is the object. JavaScript objects are rather different than objects in other languages. Fundamentally, an object in JavaScript is what is known as an associative array in other languages. Associative arrays take in strings as the keys, instead of numbers as in the arrays above. It is this aspect of objects that we will cover in this section. There are other aspects of JavaScript objects that make them much more powerful than associative arrays, but we will cover those in later sections.

Let’s start with defining an object. Instead of square brackets as with arrays, we use curly braces. And instead of each value’s key being implied by it’s position in the array, we need to supply each value of an object with a key. Each key/value pair is written as key: value with a comma separating each pair. Here is an example:

var obj = {
    jj: "hi",
    gg: 3
};

console.log(obj); // [object Object]
console.log(obj["jj"]); // hi
console.log(obj.gg); // 3

//keys in a variable
var hh = "gg";
console.log(obj[hh]); // 3
console.log(obj.hh + ' '); // undefined

//setting values
obj[hh] = 7;
obj["arr"] = ["first", 3];
obj.fun = function() {
    return "functions!";
};

console.log(obj.gg); // 7
console.log(obj.arr); // first,3
console.log(obj.arr[0]); // first
console.log(obj.fun()); // functions!

Note that when defining an object, the key is always considered a string. It would be best to enclose it in quotes, but that looks noisy. When using the notation obj["jj"], it looks up the entry with the key “jj”. But without the quotes, as in obj[jj], JavaScript looks up the value in the variable jj and then returns it as a string to be used for a key.

In order to cut down on the noise, we can also use the dot notation. If we write obj.jj it interprets this in the same way as obj["jj"].

Also observe that we can store compound data structures in objects and access them in the same way as with the arrays, namely by adding deeper access to the right.

We have been referring to the data in objects as key/value pairs. This is the associative array language. You might also think of an object as a function-as-association, whose input/output pairs are given. A dictionary.

In JavaScript, it’s much more common to refer to these keys as properties of the object, unless the value is a function in which case we refer to it instead as a method. It is best to only use this terminology for functions referencing the object itself in the code, as discussed later.

Properties and methods are the language of object-oriented programming. Since JavaScript masquerades as such a language, you should expect to hear that frequently. But in truth, a common use of an object in this language is as an associative array and we recommend thinking of them that way.

With arrays, we used a for loop to iterate over the data. With an object, we can also use a special for... in loop to iterate over it. It is best to only use this technique for objects that are used as associative arrays.

Example:

var dict = {
    hi: "a greeting",
    bye: "a parting word",
    book: "a big collection of words"
};

for (var term in dict) {
    console.log(term, dict[term]);
}

The for... in loop runs through all of the keys in the object, storing each key in the provided variable. To access the value, you must use the object access notation. The access order of the keys is not defined by a standard and should be considered random.

Mimicking the summation example in the array section, we can store numerical values in an object — such as the amount of money in various accounts — and then loop over them to get the sum.

var finances = {
    bank: 2003.51,
    credit: -100.04,
    stocks: 871.54,
    medical: -4012.54
};

var sum = 0;
for (var account in finances) {
    sum += finances[account];
}
console.log(sum.toFixed(2)); // -1237.53

//function form
var sumFun = function(toSum) {
    var sum = 0;
    for (var name in toSum) {
        sum += toSum[name];
    }
    return sum;
};
console.log(sumFun(finances).toFixed(2)); // -1237.53

The second part of the example defines a function to handle the looping, one suitable for summing the values in any object whose values are solely numerical.

Passing Objects into Functions

Objects can be passed into functions and returned by them. If you access a key of the input and store something new in it, that change is seen outside of the function. The book, if you will, is accessed directly; functions do not get a copy of the book, but rather the book itself. You can of course assign the input variable a new value and this will not generally affect the passed in book.

The following example shows what can be done, though it should not be emulated.

// A messed up function
var fun = function(anyInput) {
    var givenInput = anyInput;
    anyInput = {
        ny: true,
        nz: false,
        c: -1
    };
    var i = 0;
    for (var key in givenInput) {
        console.log(key, givenInput[key], anyInput[key]);
        givenInput[key] = i;
        i += 1;
    }
    return anyInput;
};

// base case
var sample = {
    a: "fe",
    b: "fi",
    c: "fo"
};
console.log(JSON.stringify(sample)); // {"a":"fe","b":"fe","c":"fo"}
// try out function
var newObj = fun(sample);
// new object
console.log(JSON.stringify(newObj)); // {"ny":true,"nz":false,"c":-1}
// modified old object
console.log(JSON.stringify(sample)); // {"a":0,"b":1,"c":2}
// delete "a"
delete sample.a;
console.log(JSON.stringify(sample)); // {"b":1,"c":2}

In this example, the function will take its argument, store it in givenInput, assign a brand new object to anyInput, and then iterate over the passed in object, now in givenInput, changing its values. It returns the newly created object pointer to anyInput; it would otherwise disappear.

The function explores what is happening; it is otherwise a bad idea to copy the above technique. It illustrates that when variables reference an object, it points to the object itself, never a copy. To make a copy, you can loop over the keys and copy the values. If those values are themselves objects or arrays, then you need to copy those as well, and so on. Making full copies is non-trivial in JavaScript.

The example concludes with a deletion example. To remove a key from an object, use the delete keyword. Assigning undefined will not remove the key, only the value.

What is that JSON in the code, you ask? JSON stands for JavaScript Object Notation, a creation of Douglas Crockford inspired by the literal JavaScript format for creating objects and arrays. The code is referencing a native browser object, JSON, that converts objects into strings that can then be converted back to the objects. For security reasons, it does not create or parse out functions. Most use cases for arrays and objects as associative arrays could use JSON.stringify to create a faithful string representation of it and JSON.parse to take such a string and create a new copy.

Methods

One of the most wonderful features of JavaScript is its use of functions-as-data. We can store functions in variables, arrays, and objects. We can pass them as arguments into other functions. We can return them as the output of a function. Anything you can do with any other kind of data, you can do with a function.

Why is this amazing? See our Defining Function for more information, but here we’ll discuss a special feature of associating a function with an object. Objects — and arrays — can be used to store a function just like any other data value. For example, the built-in Math object is an associative array of functions and numbers. Math["sqrt"] returns a function that will take the square root of its input. So Math["sqrt"](4); yields a value of 2 as does the more standard version Math.sqrt(4);. This has some pretty amazing uses, but it is also what we expect: functions-as-data can be stored in an associative array.

But functions in objects can do more. This is where associative arrays evolve into objects. Each object can have behavior added to it. And the behavior, called a method and enacted by functions, has full access to the object itself. It knows the object through the keyword this. Only ever use this when you are referring to the object that called the function.

As an example, suppose we are representing a line by an object and we want a function that returns y when we give an x value. Here is an implementation:

var line = {
    m: 5,
    b: 4,
    y: function(x) {
        return this.m * x + this.b
    }
};
console.log(line.y(3)); // 19

// assign line's value to label, redefine line
var label = line;
line = {
    y: function() {
        return "no line here";
    }
};
console.log(line.y(3)); // no line here
console.log(label.y(3)); // 19

The keyword this evaluates, in the first part, to the object named by line. But after reassignment, it does not get confused. It still points to itself regardless of the variable that identifies it.

Every function call has an implicit this defined. If a function is called in the global context, what this refers to varies by browser, but is often the global object itself, making it very dangerous to use. If this is used in the function, be explicit about what it refers to. To do that, there are two other ways to call a function.

Our function is called hand and this refers to the different objects as appropriate and we will call it in different ways:

var hand = function(num, name) {
    this.newnum = num;
    this.newname = name;
    console.log(num, this.whoAmI, name);
};

//initialize bob and check contents
var bob = {
    whoAmI: "Bob"
};
var alice = {
    whoAmI: "Alice"
};

hand.call(bob, 1, "Jane"); // 1, Bob, Jane
//bob changes!
console.log(JSON.stringify(bob));
// {"whoAmI":"Bob","newnum":1,"newname":"Jane"}

hand.apply(alice, [2, "Mike"]); // 2, Alice, Mike
bob.spouse = hand;
bob.spouse(3, "Alice"); // 3, Bob, Alice

//the this is the global object
hand(4, "Hermit"); // 4, undefined, Hermit
//newnum as a global variable has been defined!
console.log(newnum); // 4

The function methods call and apply take in the object that is to be this and then arguments that are passed to the function. For call, it is a usual list of function arguments. For apply, the argument after the object should be an array, and the elements of the array are passed in as the arguments of the function.

One final note about this. Every function has its own scope and in that scope, this refers to the object that called that function. If you want to pass the this object into another scope, as when making factory functions, then you need to store this in another variable.

Constructors and Prototyping

Prototyping is an advanced topic, particularly if one tries to understand all of its intricacies. We will be content with just the most basic use of it.

We have been making objects so far with the simple {key:value, key2:value2} literal notation. But another way to construct an object is to use a constructor function. This function is given inputs as usual and returns an object. While any function can, theoretically, be used to do this, generally it is a special function. To construct an object, one uses the keyword new followed by an invocation of the function. Here is an example:

// The constructor
var Line = function(data) {
    // Initialize with specific data
    for (var name in data) {
        this[name] = data[name];
    }
    return this;
};

// The constructor's prototype object
// Two default properties and one default method
Line.prototype = {
    m: 1,
    b: 0,
    y: function(x) {
        return this.m * x + this.b;
    }
};

// Create two lines, one a default
var lineA = new Line({
    m: 3,
    b: 4
});
var lineB = new Line();

// .y refers to prototype's function
console.log(lineA.y(2)); // 10
console.log(lineB.y(2)); // 2

The code above first creates a function called Line — this is our constructor. Note that this refers to the newly created object. Returning it is the default behavior, but it is good form to explicitly include return this;. The constructor function can be empty or it can have initialization code, data checking, data transformations, etc.

Next, we assign a prototype object to Line. Whenever the new object fails to have an entry for a given key, it will check the keys in the prototype object. Here we have as prototype a default slope of 1, a y-intercept of 0, and a function that returns the y-value given an x-value. A good alternate strategy is to have no default slope or intercept values and instead signal an error, such as returning false.

We finally get to create the objects using the syntax new Line(...). The new keyword tells JavaScript to create a new object using the Line constructor. The new object will have access to the prototype of the constructor Line.

We finish by computing the y-value of each line for the input value 2. lineA has a value of \(3*2+4 =10\) while lineB is using the default slope and intercept to obtain \(1*2+0 = 2\).

Now that we have done that, let us continue with this example below and give lineA its own y function, hiding the prototype version from lineA. You might do this, for example, if lineA was vertical or, in this case, horizontal (m=0 would also do the trick). After that line, we modify the prototype of Line so that y is now a new function. As evidenced by the next line, this is immediately respected by lineB which uses the new function.

// Give lineA a new y function
lineA.y = function() {
    return -1;
};

// Redefine the prototype y function
Line.prototype.y = function(x) {
    return 2 * this.m * x + this.b;
};

// A uses its y, lineB uses new prototype
console.log(lineA.y(2)); // -1
console.log(lineB.y(2)); // 4

// Get rid of A's y
delete lineA.y;
// Both use new prototype
console.log(lineA.y(2)); // 16
console.log(lineB.y(2)); // 4

The final manipulation above is to get rid of the custom behavior of lineA.y and return to the prototype object by using the delete keyword. The removal of the key from the object reveals the prototype underneath. Note that assigning a value of undefined would not do this; the key still exists in that case. Assigning a value of null is a good way to hide an underlying prototype key without supplying a value.

Both data and methods can be put in the prototype object. Typically it is methods that are used in prototyping. As an example of using data, imagine constructing dictionaries for different dialects of English, say American vs. British. Many of the words are the same. They can be loaded into a common prototype. Then each specific language’s dictionary can be implemented as objects constructed from this common dictionary.

The underlying prototype is live so it is not like copying a book and then adding in text. Rather, it is like having a three volume set: one for the American and one for the British dialects (the particular objects) which are consulted first before the common dictionary (the prototype) is consulted. And that common dictionary can be modified at any time.

When using the for... in loop over an object, please note that it will include the non-native keys in prototype objects. To filter those out, you must use the generic object method hasOwnProperty:

// for--in checking
var key;

// m and b are part of lineA, but not y
for (key in lineA) {
    if (lineA.hasOwnProperty(key)) {
        console.log(key + ": " + lineA[key]);
    } else {
        console.log(key + " is not part of lineA");
    }
}

// All keys are not part of lineB
for (key in lineB) {
    if (lineB.hasOwnProperty(key)) {
        console.log(lineB[key]);
    } else {
        console.log(key + " is not part of lineB");
    }
}

This will report for lineA, from the previous example, that m and b are properties of lineA, but that y is not. For lineB, it does not have any of its own properties.

We conclude this running example with examples of checking the type of an object. It is much more reliable to check for existence of methods or properties rather than what constructed a function. Nevertheless, lineA instanceof Line will return true. There is also a typeof operator, but this only returns the type of native objects. For most objects, it will return Object. See the typeof entry at MDC. Note that arrays are typed as Object though they are instances of Array. Just don’t try to put a type on things in this language.

// Checking type. Not so useful.
console.log(typeof lineA); // object

// A little better
console.log(lineA instanceof Line); // true 

var mer = Line;
// It is the function itself, not the name
console.log(lineA instanceof mer); // true

We can think of prototyping as either generating sensible defaults with common methods or as generating a foundational associative array upon which different specializations can be built upon. Both are useful models and we will be using both. The first way, however, is the mental model that you are likely to encounter in object-oriented discussions.

Arrays as Objects

Arrays are objects. While the data they contain is generally accessed by natural numbers, starting from 0 up to the length of the array, it can also be used as an object with access to “string” named properties and methods. It is, in general, a bad idea to store data in an array as if it were an associative array. But it can be useful to extend its prototype.

We have used the notation [data, ....] to generate an array. This is literal notation. One can also use a constructor: new Array(data, ...). It is rare to see someone doing that. But the literal notation behaves as if this is done. That is, all arrays use Array.prototype for the behavior. And that prototype object does not start empty.

Arrays are natively prototyped with one property and several methods. In the table below, the “!” indicates a method which modifies the array itself.

Key Value Description
length Number # items in array
concat f(data, …)→array Makes array out of this and data
join f(sep)→str Makes a string out of array using sep
! pop f()→value Deletes last item returning it
! push f(data,…)→num Adds data to end of array returning new length
! shift f()→value Deletes first item returning it
! unshift f(data,…)→num Adds data to beginning of array returning new length
slice f(start,end)→array Returns array of start <= i < end
! splice f(start,len,data,…) →array Removes elements from start up to a length of len, replacing with data and returning removed elements
! reverse f()→this Reverses array
! sort f(fun)→this Sorts using optional function fun
toString f()→str Returns a string representing array

See the Mozilla Array reference page for more details. There are some more advanced Array methods that we have not included here; they have only recently been added to all mainstream browsers.

Here are examples of all of these methods in action:

var jt = [10, "a", 5];

// Using join to make string
console.log("length, join: " + jt.length, JSON.stringify(jt));
// length, join: 3, [10,"a",5]

var con = jt.concat(7, 8, [4, 2], [[3, 4], [5]]);
// Notice the arrays are flattened by concat
// but not the array in an array
console.log("concat: " + con.length, JSON.stringify(con));
// concat: 9, [10,"a",5,7,8,4,2,[3,4],[5]]

// jt is unchanged
console.log("concat jt:" + jt.length, JSON.stringify(jt));
// concat jt:3, [10,"a",5]

// Arrays and objects are equal only if same reference
// Contents of objects are NOT compared to test equality.
var otherjt = [10, "a", 5];
var labeljt = jt;
console.log("array equality: " + (otherjt == jt), labeljt === jt);
// array equality: false, true

var p = jt.pop();
console.log("pop: " + p, jt.length, JSON.stringify(jt));
//  pop: 5, 2, [10,"a"]

// Notice labeljt points to the modified array
console.log("labeljt: " + JSON.stringify(labeljt));
// labeljt: [10,"a"]

// push returns the length of changed array
console.log("push: " + jt.length, jt.push("hello"), jt.length);
// push: 2, 3, 3
console.log("push: " + JSON.stringify(jt));
// push: [10,"a","hello"]

// shift and unshift do pop and push at the beginning
var s = jt.shift();
console.log("shift: " + s, jt.length, JSON.stringify(jt));
// shift: 10, 2, ["a","hello"]

console.log("unshift: " + jt.length,
                  jt.unshift("bye", "again", [7, 4]));
// unshift: 2, 5
console.log("unshift: " + jt.length, JSON.stringify(jt));
// unshift: 5, ["bye","again",[7,4],"a","hello"]

var sli = jt.slice(1, 4);
console.log("slice: " + sli.length, JSON.stringify(sli));
// slice: 3, ["again",[7,4],"a"]
console.log("slice: " + jt.length, JSON.stringify(jt));
// slice: 5, ["bye","again",[7,4],"a","hello"]

// splice uses length to determine end of segment
var spli = jt.splice(1, 3, "new", "old", [5, 3]);
console.log("splice: " + spli.length, JSON.stringify(sli));
// splice: 3, ["again",[7,4],"a"]
console.log("spliced: " + jt.length, JSON.stringify(jt));
// spliced: 5, ["bye","new","old",[5,3],"hello"]

console.log("reverse: " + JSON.stringify(jt.reverse()));
// reverse: ["hello",[5,3],"old","new","bye"]
console.log("reversed: " + jt.length, JSON.stringify(jt));
// reversed: 5, ["hello",[5,3],"old","new","bye"]

console.log("sort: " + JSON.stringify(jt.sort()));
// sort: [[5,3],"bye","hello","new","old"]
console.log("sorted: " + jt.length, JSON.stringify(jt));
// sorted: 5, [[5,3],"bye","hello","new","old"] 

console.log("toString: " + jt.toString());
// toString: 5,3,bye,hello,new,old

One special method of note is the sort method. It takes an optional function argument which is used to compare the entries in the array. This is very useful when sorting compound data structures as demonstrated below. The compare function’s return argument should be -1 to leave the items in place, 1 if they are to be exchanged.

//extended sort example with function
//extended sort example with function
var tp = [[1, 2], [4, 3], [3, 4], [5, 7], [0, 5]];
console.log("original ordering: " + JSON.stringify(tp));
// original ordering: [[1,2],[4,3],[3,4],[5,7],[0,5]]

var compFirst = function(a, b) {
    if (a[0] <= b[0]) {
        return -1;
    } else {
        return 1;
    }
};
tp.sort(compFirst);
console.log("ordering by first: " + JSON.stringify(tp));
// ordering by first: [[0,5],[1,2],[3,4],[4,3],[5,7]]

One other note about arrays. It is best for performance reasons to store the array length in a local variable if one is going to loop over it. So do this:

var arr = [11, 5, 7];
var n = arr.length;

for (var i = 0; i < n; i += 1) {
    console.log(arr[i]);
}

Why do we need n? Because the loop calls the statement each time and accessing a property of an object is more resource intensive than accessing a local variable. This also guards against infinite loops if one accidentally changes the length of the array in the loop.

Arguments of Functions

Now that we fully understand arrays, we can talk about the arguments of a function. Yes, we have named arguments. But we do not need them.

All functions have a local variable defined called arguments. This is a an array-like object, meaning it has a length and it can be accessed like an array: arguments[i].

You can use the length property to determine the number of arguments called and to use it to loop over all the passed in arguments.

One way to use this is for a function that takes in a variable number of arguments. For example, the sum function below takes any number of values and adds them up, returning the final summation.

var sum = function() {
    var n = arguments.length;
    var i;
    var sum = 0;
    for (i = 0; i < n; i += 1) {
        sum += arguments[i];
    }
    return sum;
};

console.log(sum(2, -3, 6, 9, 12, 11)); // 37
console.log(sum(1.2, 3.3, 5.7)); // 10.2

// pass in an array as arguments
console.log(sum.apply(null, [1.2, 3.3, 5.7])); // 10.2

var passthrough = function() {
    console.log(sum.apply(null, arguments));
};
passthrough(1, 5, 7); // 13

Notice in the code above that we used the .apply to pass an array into sum. The apply unpacks the array into the arguments of the function. Also notice that we used null as the value for the assigned value of this. We recommend emulating that convention for when a function is not intended to be used as a method.

You can also use arguments as an array in calling .apply. This allows for an easy way to pass on the inputs to another function. If you use .call, then the arguments array is passed in as input, instead of the values in that array.

If you need to convert the arguments object into a proper array, you can use the following trick from the MDC:

var args = Array.prototype.slice.call(arguments);

Function Data

Just as arrays can have properties and methods, so too can functions. There is a Function constructor and it has a prototype. For the most part, only the apply and call methods are used from the prototype.

But for each function, we can use it as an object. This gives us great power. We can set function parameters in this fashion. For this, we use the name of the function to access the parameters from within the function itself.

This example is of a line whose slope and intercept can be changed.

var line = function me(x) {
    return me.m * x + me.b;
};

line.m = 5;
line.b = 4;
console.log(line(3)); //19

line.m = 1;
console.log(line(3)); // 7

line.slope = function(x) {
    console.log(this.m, this(x));
};
line.slope(3); // 1, 7

delete line.m;
console.log(line(3)); // NaN

This example demonstrates how a function can be changed by changing its parameters. The internal name of the function in this example is me. It can be anything, but since it is not seen outside of the function, you might want to settle on a convention similar to that of this.

Please note that the function’s internal name can be hidden by a local variable declaration of the same name.

We have presented this feature as setting a few parameters and this is the most likely use case. But one can store any kind of data including other functions, even methods; this will point to the function and can even be called from with one of its own methods as we did above with line.slope.

So Many Choices

We have now detailed several different ways to construct a family of functions. This is the power and flexibility of JavaScript. It is exciting to those of us who code as a hobby. It is a nightmare for career programmers. Each of the choices has the potential to impact the code in terms of speed, memory usage, data protection, and lines of code, but the most important factor in a choice is the answer to the question: What is your audience comfortable with? The audience might just be you or it could be somebody you will never meet. A good programmer is someone whose code is easy to maintain. Thus, whatever you use, it should feel natural to the problem and coding paradigm you find yourself in.

For most programmers, the Object is primary. So having a Line constructor is very natural for them. For mathematicians, the function factory might actually make more sense to them. Since this site is about programming math, expect function factories to appear often. This last construct, a single function whose parameters change, is a hybrid and is probably more likely to appear in very exploratory examples. For example, fitting a type of function to data might fall naturally into this model. Rather than create and discard several likely candidates, we could just make one basic function whose parameters change. This is also a very natural mental model for mathematics.

In the end, it is fun to use different styles and notions of coding and we are all about fun. So concludes the basics on programming in JavaScript. The next page, Templating Mathematics, explores our other tools.

Leave a Comment

Leave a Reply