In the first version of JavaScript, there were no arrays. They were later introduced as a sub-class of that "mother of all objects": Object. You can test this quite easily by doing this:
var foo = [1,2,3,4];
for (var n in foo)
{//check if n is equal (value and type) to itself, coerced to a number
    console.log(n === +(n) ? 'Number' : 'String');
}
This will log String, time and time again. Internally, all numeric keys are converted to strings. The Length property merely fetches the highest index, and adds 1 to it. Nothing more. When you display your array, the object is iterated, and for each key, the same rules apply as for any object: first the instance is scanned, then the prototype(s)... so if we alter our code a bit:
var foo = [1,2,3,4];
foo[9] = 5;
for (var n in foo)
{
    if (foo.hasOwnProperty(n))
    {//check if current key is an array property
        console.log(n === +(n) ? 'Number' : 'String');
    }
}
You'll notice the array only has 5 own properties, the undefined keys 4-8 are undefined, because there was no corresponding value found within the instance, nor in any of the underlying prototypes. In short: Arrays aren't really arrays, but objects that behave similarly.
As Tim remarked, you can have an array instance with an undefined property that does exist within that object:
var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true
But again, there is a difference:
console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false
RECAP, your three main questions:
- Arrays are objects, that allow you to reference their properties with numeric instances 
- The - undefinedvalues are not there, they're merely the default return value when JS scans an object and the prototypes and can't find what you're looking for: "Sorry, what you ask me is undefined in my book." is what it says.
 
- Working with largely undefined arrays doesn't affect the size of the object itself, but accessing an undefined key might be very, very marginally slower, because the prototypes have to be scanned, too.
Update:
Just quoting the Ecma std:
15.4 Array Objects
  Array objects give special treatment to a certain class of property names. A property name P (in the form of a 
  String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 
  2^32
  1. A property whose property name is an array index is also called an element. Every Array object has a 
  length property whose value is always a nonnegative integer less than  2^32. The value of the  length
  property is numerically greater than the name of every property whose name is an array index; whenever a 
  property of an Array object is created or changed, other properties are adjusted as necessary to maintain this 
  invariant. Specifically, whenever a property is added whose name is an array index, the length property is 
  changed, if necessary, to be one more than the numeric value of that array index; and whenever the length
  property is changed, every property whose name is an array index whose value is not smaller than the new 
  length is automatically deleted. This constraint applies only to own  properties of an Array object and is 
  unaffected by length or array index properties that may be inherited from its prototypes.
An object, O,  is said to be sparse if the following algorithm returns true:
  1. Let len be the result of calling the [[Get]] internal method of O with argument "length".
  2. For each integer i in the range 0≤i
  
    a. Let  elem be the result of calling the [[GetOwnProperty]] internal method of  O with argument 
         ToString(i).
  
     b. If elem is undefined, return true.
  3. Return false.