Prerequisites:
Arrays are actually objects, that is, hash tables; so they work well for sparse arrays, and they grow dynamically if you insert new values that extend the size of the array. They are similar to Python’s lists and to Java’s ArrayLists.
You can write an array literal expression like this:
var ws = [1, 2, "three", "four"]; // an array with four elements
Or you can construct an array with new Array(...):
var xs = new Array(10); // an array of 10 undefined elements
var ys = new Array("cat", "dog"); // an array with two elements
var zs = new Array(1, 2, "three", "four"); // like ws
Generally, if you know the elements of the array in advance, using an array literal is better than constructing an array (with new); it is more efficient.
We refer to array elements using the notation xs[i], where xs is the array and i is the subscript. Subscripts start at 0, as in Python and Java.
console.log(ys[0]); // prints "cat"
xs[5] = "puppies"; // stores "puppies" in xs[5]
The length property is the size, or number of elements, in the array.
ws.length // is 4
ws["length"] // is 4
It can be adjusted:
ws.length = 8; // increases size, new elements are undefined
ws.length = 3; // removes elements
To iterate through all the elements of an array:
for (var i = 0; i < ws.length; i++)
console.log(ws[i]);
Or, going backwards:
for (var i = ws.length - 1; i >= 0; i--)
console.log(ws[i]);
Arrays are objects, so it shouldn’t be surprising that they have methods.
Non-destructive methods produce a new object, instead of mutating an existing object.
Concatenate (chain together) two arrays:
[1, 3, 5].concat([7, 8]) // value is [1, 3, 5, 7, 8]
Or using variables:
xs.concat(ys)
Remember, this is non-destructive, so both xs and ys are unchanged.
Concatenate three arrays, similarly:
xs.concat(ys).concat(zs)
Equivalently:
xs.concat(ys, zs)
This can go on with any number of array arguments.
join forms a string from the array elements; sep is the separator, the default is a comma:
xs.join(sep)
Examples—here, as you can see from the js> prompt, I am using SpiderMonkey as a stand-alone Java interpreter; but you could equally well type these examples into your web browser’s Java console:
js> xs = [10, 1, 2, 3];
"10,1,2,3"
js> xs.join()
"10,1,2,3"
js> xs.join(";")
"10;1;2;3"
js> xs.join("!@#")
"10!@#1!@#2!@#3"
slice works conceptually like slicing a list in Python, but the notation is different: it’s a method call.
// xs.slice(start[, end])
js> xs.length
7
js> xs.slice(0, 4)
["a", "b", "c", "d"]
js> xs.slice(4, 7)
["e", "f", "g"]
js> xs.slice(4)
["e", "f", "g"]
js> xs.slice(2, 5)
["c", "d", "e"]
toString converts to a string (not usually needed explicitly).
js> xs
["a", "b", "c", "d", "e", "f", "g"]
js> xs.toString()
"a,b,c,d,e,f,g"
Destructive methods mutate the array.
An array can be operated on as a deque (a data structure that supports both stack and queue operations), with push and pop operating at the rear, unshift and shift at the front.
push adds an element to the rear of an array; it returns the new length.
pop removes and returns the rear element of an array.
js> var xs = [1, 2, 3];
js> xs
[1, 2, 3]
js> xs.push(10);
4
js> xs
[1, 2, 3, 10]
js> xs.pop()
10
js> xs
[1, 2, 3]
unshift adds an element to the front of an array, returning the new length.
shift removes and returns the front element of an array.
js> xs
[1, 2, 3]
js> xs.unshift(10);
4
js> xs
[10, 1, 2, 3]
js> xs.shift();
10
js> xs
[1, 2, 3]
xs.sort(); // sorts alphabetically
xs.sort(function (x, y) { return x - y }); // sorts numerically
The optional compare function argument should return a negative value from compare(a, b) if a < b, 0 if a = b, and positive if a > b.
Destructively reverses the elements of the array
xs.reverse();
Destructively adds and/or removes elements anywhere in an array
xs.splice(i, n [, e1, e2, ...]);
// where i = position at which to add or remove elements
// n = number of elements to remove
// e1, e2, ... = elements to be inserted
// Returns the elements removed
Examples:
js> xs
[10, 1, 2, 3]
js> xs.splice(1, 0, 'a', 'b', 'c');
[]
js> xs
[10, "a", "b", "c", 1, 2, 3]
js> xs.splice(1, 3);
["a", "b", "c"]
js> xs
[10, 1, 2, 3]
js> xs.splice(2, 2, 'cat', 'dog');
[2, 3]
js> xs
[10, 1, "cat", "dog"]
A function can be called with more or less actual parameters than it has formal parameters. All the actual parameters are available through a “property array”, arguments; arguments.length is the number of actual arguments, and the arguments are arguments[0] … arguments[arguments.length - 1].
Extend Example 07-01 using an array of ball objects, so that the cannon can fire repeatedly with multiple balls in the air.
Well, of course, we are going to create a lot of cannon balls, so it would be a good idea to define a function to make them:
function start_animation () {
...
function makeBall () {
return {'x': 0, 'y': 0, // position,
'vx': init_speed * Math.cos(theta),
'vy': init_speed * Math.sin(theta), // velocity
'ax': 0, 'ay': -9.8, // acceleration, meters/sec/sec
'radius': 10, // pixels
'color': 'black'};
}
Instead of a single ball variable, we want an array of balls, initially empty:
var balls = [];
...
Let’s fire the cannon every 3 seconds (fire_dt) and keep going for 20 seconds (stop_dt).
We need an interval timer (firing_iid) to fire the cannon, and a timeout (stop_tid) to make the cannon stop firing. But when the shooting stops, we don’t want the animation to suddenly end, with the balls in mid-air. Instead, we’ll need to wait for the last ball to return to terra firma; this will come in physics.
var fire_dt = 3 * 1000; // fire every 3 seconds
var stop_dt = 20 * 1000; // stop in 20 seconds
// Interval and timeout timers, initialized later
var physics_iid, render_iid;
var firing_iid;
var stop_tid;
The physics function has a few changes.
First, it uses a loop to iterate through the array, updating each ball.
Second, if a ball is out of range, instead of stopping the animation (as we did when there was only one ball), we just remove it from the array.
Third, when the last ball has gone out of range, it is time to stop the animation. Now when this happens, physics will have just removed the last ball from balls, and so balls will be empty, and that is how we know that the last ball has landed;1 physics will then clear the remaining interval timers, so that we don’t waste CPU cycles.
// physics update
function physics() {
var dt = 0.001 * physics_dt; // milliseconds to seconds
for (var i = 0; i < balls.length; i++) {
var b = balls[i]; // short variable name
// update velocity vy; vx does not change
b.vy = b.vy + b.ay * dt;
// update position
b.x = b.x + b.vx * dt;
b.y = b.y + b.vy * dt;
// remove if out of range
if (b.y < 0 || b.y > CH || b.x < 0 || b.x > CW) {
console.log("Deleting ball at " + i + ": " + b);
balls.splice(i, 1); // remove 1 element at balls[i]
}
}
// stop when the last ball has come to earth
if (balls.length == 0) {
clearInterval(physics_iid);
clearInterval(render_iid);
}
}
There are changes to rendering, since we now have several balls to render. First, we have render_balls instead of render_ball. Then, of course, there’s a loop to render each ball in the array balls.
// animation update
function render () {
render_sky();
render_balls();
};
...
function render_balls () {
for (var i = 0; i < balls.length; i++) {
var b = balls[i];
ctx.beginPath();
ctx.arc(xcc(b.x), ycc(b.y), b.radius, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fillStyle = b.color;
ctx.fill();
}
}
...
The fire_cannon function creates a ball and pushes it onto the balls array (so that it becomes the last element).
function fire_cannon () {
balls.push(makeBall());
}
The stop_cannon function clears the interval timer for the cannon, so that the cannon stops firing.
function stop_cannon () {
clearInterval(firing_iid);
// physics will stop everything else
// when balls is empty
}
Finally, after defining all these inner functions, we have a few more steps needed to get things going. We start by firing the cannon once (fire_cannon()). Then we have two additional timers to set up: one to keep the cannon firing every few seconds (firing_iid), and one to stop the cannon (stop_tid).
fire_cannon();
render();
physics_iid = setInterval(physics, physics_dt);
render_iid = setInterval(render, render_dt);
firing_iid = setInterval(fire_cannon, fire_dt);
stop_tid = setTimeout(stop_cannon, stop_dt);
}
start_animation();
It could be more complicated than this, because balls is also empty at the start of the simulation, before the cannon has ever fired. Fortunately, the cannon fires before physics ever runs, so we do not need an additional variable to tell us that the cannon has stopped firing (instead of not yet started).↩