Ying Xiong
Ying Xiong

Reputation: 4938

Javascript: order of parent/child object instantiation

I am new to JavaScript, and would like to understand more about the order it instantiates parent/child objects. More specifically, I would like to understand the following snippet from the compiler/browser perspective.

var parent = {
  child: {
    field1: "value1",
    field2: "value2"
  },
  someOtherField: parent.child.field1, // => Error
  someOtherField: this.child.field1    // => Error
};

Both Error lines in the snippet above will generate the same error

Uncaught TypeError: Cannot read property 'field1' of undefined

So it seems when the browser looks at someOtherField line, parent.child is still undefined, and therefore parent.child.field1 is illegal. Could someone share some insights on why this is happening, and what is the exact sequence of instruction is the browser trying run with the above code snippet? Thanks!

Upvotes: 0

Views: 120

Answers (2)

RobG
RobG

Reputation: 147453

In the code (dealing with one issue at a time):

var parent = {
  child: {
    field1: "value1",
    field2: "value2"
  },
  someOtherField: parent.child.field1 // => Error
};

The variable parent is created and initialised to undefined before any code is executed (so–called "hoisting"). Once execution begins, inspection of the progressive results appear to be that first the object is assigned to parent, then the internal properties are resolved.

So the first error message is like:

TypeError: undefined is not an object (evaluating 'parent.child.field1')

since parent is an object. From this result it's impossible to tell whether parent has a child property or not since it may have a child property initialised to undefined, or it might not have a child property at all. Either way, parent.child resolves to undefined and attempting to resolve a property of undefined returns a type error.

Note that given:

var parent = {
  one: 'one',
  two: parent.one
}

console.log(parent.one, parent.two) // one undefined

it can be seen that at the point of assigning a value to parent.two, parent.one was undefined.

As for:

  someOtherField: this.child.field1

the value of this will be the global object (window in a browser), and it doesn't have a child property so you get a similar error:

TypeError: undefined is not an object (evaluating 'this.child.field1')

since there is no global child, so this.child returns undefined and attempting to resolve a property of undefined throws a type error.

Note also that:

  someOtherField: this.parent.child.field1

is equivalent to:

  someOtherField: parent.child.field1

and throws the same error.

The bottom line is that you shouldn't attempt to assign properties of partially completed objects. I'm sure there's an appropriate reference in ECMA-262, it will take a little while to find it.

Upvotes: 1

Aadit M Shah
Aadit M Shah

Reputation: 74234

This has nothing to do with parent/child object instantiation. It has everything to do with hoisting.

All declarations in JavaScript are hoisted. Remember that variable declaration and variable definition are two separate things. For example, we can declare a variable x using var x. We define x using x = someValue. We can combine variable declaration and variable definition into a single construct as var x = someValue. However we are still declaring the variable and then defining it.

As I said all declarations in JavaScript are hoisted (i.e. they are lifted to the top of the function scope that they are declared in). For example, consider:

alert(x);     // declared but not defined (undefined)
var x = null;
alert(x);     // declared and defined (null)

Here the variable declaration var x has been hoisted to the top of the scope. Variables that have been declared but have not been defined have the value undefined by default.

Compare the above code with:

try {
  alert(x);
  x = null;
  alert(x);
} catch(e) {
  alert(e);  // ReferenceError: x is not defined
}

Here we have not declared x. Hence x is not hoisted. Thus when we try to print the value of x we get a ReferenceError. Note that it is not undefined because we haven't even declared it.

What's happening in your code

Now let's consider your code:

var parent = {
  child: {
    field1: "value1",
    field2: "value2"
  },
  someOtherField: parent.child.field1 // => Error
  someOtherField: this.child.field1   // => Error
};

This is the same as:

var parent; // hoisted; undefined

parent = {
  child: {
    field1: "value1",
    field2: "value2"
  },
  someOtherField: parent.child.field1 // => Error
  someOtherField: this.child.field1   // => Error
};

Before defining parent, it is undefined. Hence parent.child.field1 is undefined.child.field1 which is an error. Also this does not point to the object literal. Your mistake is that parent is not defined until after the object literal has been assigned to the variable (which is after parent.child.field1 is evaluated).

What you can do is:

var parent = {
  child: {
    field1: "value1",
    field2: "value2"
  }
};

parent.someOtherField = parent.child.field1;

Hope that helps.

Upvotes: 0

Related Questions