Index 4 "isn't undefined"... even though it doesn't exist


#1

error message: "It looks like the Person at index 4 isn't named undefined"

var family = [];
function Person(name, age) {
    this.name = name,
    this.age = age,
    family.push(this);
};
var alice = new Person("alice", 40);
var bob = new Person("bob", 42);
var michelle = new Person("michelle", 8);
var timmy = new Person("timmy", 6);
// loop through our new array
for (i=0; i<family.length; i++) {
    console.log(family[i].name);
};

Now I know I probably wasn't expected to add to the array as part of new Person, but it appears to work. This code does in fact print out each name in the correct order, and if I change i<family.length to i<5, the fifth name (at index 4) prints undefined. So why am I getting this error?


#2

When we query a non-existent element JavaScript responds with undefined. This is not a string, but a built in response.

While it may work as expected, it is not advisable to write an array mutation method inside a constructor function. Constructors should be re-usable which means dedicated variable names don't really have a place in them. Functions as a whole should be generic and re-usable to take full advantage of their utility.

family.push(new Person("alice", 40));
family.push(new Person("bob", 42));
family.push(new Person("michelle", 8));
family.push(new Person("timmy", 6));

#3

When we query a non-existent element JavaScript responds with undefined. This is not a string, but a built in response.

So... the error message is actually irrelevant, and it just isn't accepting it because the addition to the array is in the constructor? Because it sounds like you're agreeing that index 4 is, in fact, undefined. So I shouldn't be getting an error message that it isn't undefined. I don't see any possible way "undefined" could have become a string.

Huh. Pulling family.push out does seem to fix it... I just thought there should be an easier way that typing it out every single time. My thought process was "If you can say all new Person will be species:homo sapien, shouldn't there be a way to say all new family members are part of the family array" but maybe that wouldn't normally be an array. Another object maybe? How would you normally specify that all of a certain type of object are part of a larger classification?

Thanks!


#4

"... this is not a string, ..."

should have read, "this is not a value or object", simply not defined, so technically, nothing. Even in an array with declared elements, such as,

var array = new Array(10);

the elements are still not defined. If we query any elements, existing or not JavaScript will respond with undefined. There is no defined value or object in any of those elements.

I would like to try to reproduce the error message you received by am having a devil of a time locating this lesson. What lesson number is it, please?


#5

Okay, found the lesson (26/33), and tried your code from the OP, which passed as is. Changing one thing,

for (var i = 0; i <= family.length; i++) {
    // ...
}

produced this error message...

Oops, try again. It looks like you have an error in your code. Here's the message: TypeError: Cannot read property 'name' of undefined

which makes perfect sense, The loop is attempting to iterate over a non-existing element so there will be no object there with the property, name. undefined is not an object so has no properties.


#6

Now I can see where you are going by including the push() method in the constructor... It seems more efficient and compact. The only issue I have with that is as stated above, it uses a dedicated variable inside the function which means every time the constructor is invoked, this ends up in the one dedicated array. What if we have multiple arrays? Do we re-write the constructor for each array? That would not be efficient, so that angle goes out the window.

A constructor is a function that does allow a return value, so we could include a third parameter, an array name:

function Person(name, age, array){
    this.name = name;
    this.age = age;
    return array.push(this);
}
var family = [];
new Person("alice", 40, family);
new Person("bob", 42, family);
new Person("michelle", 8, family);
new Person("timmy", 6, family);

for (i=0; i<family.length; i++) {
    console.log(family[i].name);
}

Output

alice
bob
michelle
timmy

Of course the SCT doesn't favor this approach since it evades the lesson instructions and the provided set up (to which we add one more line).

Oops, try again. Make sure that you use the Person constructor when creating the new Person and adding it to family

And, I will add that it is non-standard from what I can tell. But it works as expected, so you may be on to something, here.


#7

It's difficult to open a can of worms and then avoid cleaning up the mess so I dug around on StackOverflow and found this thread:

What values can a constructor return to avoid returning this?

Based upon what we read there, this looks like a legitimate constructor:

function Person(name, age, array){
    this.name = name;
    this.age = age;
    return array.push(this);
}
function listArray(array){
    for (var i = 0; i < array.length; i++) {
        console.log(array[i].name);
    }    
}
var family = [];
new Person("alice", 40, family);
new Person("bob", 42, family);
new Person("michelle", 8, family);
new Person("timmy", 6, family);

listArray(family);

var bill = new Person("bill", 50, family);
console.log(bill);

listArray(family);

Output

alice
bob
michelle
timmy
{ name: 'bill', age: 50 }  // `this` is returned to `bill`, as expected
alice
bob
michelle
timmy
bill                       // but it is still pushed to the family array

Interestingly enough, the above code passes 26/33 (4.1).


#8

Thanks for going through all that! I think I do follow a little better why that would not be the normal construction (although it's interesting that it passed for you), and how it might sometimes work.

Actually, having now finished the Javascript course, I think the concept I was attempting to get at was closer to later lessons about prototypes and inheritance. FamilyMember.prototype = new Person(); or something like that would probably be more useful in practice than including an array in a constructor.


#9

Keeping the following,

function Person(name, age, array){
    this.name = name;
    this.age = age;
    return array.push(this);
}
function listArray(array){
    for (i=0; i < array.length; i++) {
        console.log(array[i].name);
    }    
}

and adding this,

function Family(surname){
    this.surname = surname;
    this.members = [];
}
Family.prototype.member = function (name, age) {
    new Person(name, age, this.members);
};

we can now do this,

jones = new Family('Jones');

jones.member("Bill",50);
jones.member("Joan",49);

listArray(jones.members);

Output

Bill
Joan

Logging the object itself,

console.log(jones);

Output

{ surname: 'Jones',

members: [ { name: 'Bill', age: 50 }, { name: 'Joan', age: 49 } ] }


#10

i don't understand why this, original post, isn't acceptable for the lesson? this is a very commonly used syntax and is currently being taught as a best practice is dev bootcamps.


#11

Actually, once the minor syntax errors are repaired, the OP works and passes...

function Person(name, age) {
    this.name = name;
    this.age = age;
    family.push(this);
}
var family = [];
var alice = new Person("alice", 40);
var bob = new Person("bob", 42);
var michelle = new Person("michelle", 8);
var timmy = new Person("timmy", 6);
for (i=0; i<family.length; i++) {
    console.log(family[i].name);
    for (var key in family[i]) {
        console.log(key, family[i][key]);
    }
}

Output

alice
name alice
age 40
bob
name bob
age 42
michelle
name michelle
age 8
timmy
name timmy
age 6

#12

why the extra for loop with the key/value pairs?


#13

For demonstration purposes. The individual names are printed to satisfy the SCT.