Hi,
To explain why the output occurs as it does, let’s have a look at what the code is doing:
Initially, tree
is an empty object.
We then add a bunch of methods to it:
console.log(tree);
> Object {
decorate: function,
getDecorator: function,
RedBalls: function,
BlueBalls: function,
Angel: function
}
We then change the value of tree, by assigning it the return value of tree’s getDecorator
method, when invoked with the parameter 'BlueBalls'
tree = tree.getDecorator('BlueBalls');
console.log(tree);
> tree.BlueBalls {
decorate: function,
getDecorator: function,
RedBalls: function,
BlueBalls: function,
Angel: function
}
So what has gone on there?
Inside tree
’s getDecorator
method, we have:
tree.getDecorator = function (deco) {
tree[deco].prototype = this;
return new tree[deco];
};
this
refers to our tree object.
tree[deco]
resolves to tree.BlueBalls
, which is a property of the tree
object.
The value of tree.BlueBalls
is an anonymous function.
The prototype property of tree.BlueBalls
is initially the Function prototype object.
The method then sets the prototype property of tree.BlueBalls
to tree
.
And returns a new instance of tree.BlueBalls
It’s worth noting that when the new operator is invoked, it first creates a new object:
tree.BlueBalls {}
The [[prototype]] property of tree.BlueBalls
(the new object) is then set to the current object value of the tree.BlueBalls.prototype
tree.BlueBalls {
__proto__: Object
Angel: function () {
BlueBalls: function () {
RedBalls: function () {
decorate: function () {
getDecorator: function (deco) {
The tree.BlueBalls
function is also executed, which overides the decorate()
method in the tree
super-class:
tree.BlueBalls {
decorate: function () {
__proto__: Object
Angel: function () {
BlueBalls: function () {
RedBalls: function () {
decorate: function () {
getDecorator: function (deco) {
We can demonstrate this, thus:
console.log(tree.decorate)
> function () {
this.BlueBalls.prototype.decorate();
console.log('Add blue balls');
}
console.log(tree.__proto__.decorate);
> function () {
this.BlueBalls.prototype.decorate();
console.log('Make sure the tree won\'t fall');
}
Now we can kick this off:
tree = tree.getDecorator('BlueBalls');
tree.decorate();
> Make sure the tree won't fall
Add blue balls
What has happened is that we have invoked tree
’s decorate method.
This in turn invokes it’s prototype’s decorate method.
The prototype’s decorate method outputs Make sure the tree won't fall
to the console.
Execution returns to tree
’s decorate method, it logs Add blue balls
to the console.
Now, what happens when we add tree = tree.getDecorator('Angel')
?
The variable tree
(which at this point is an instance of tree.BlueBalls
), doesn’t have a getDecorator
method, so JavaScript checks its [[prototype]] to find one.
tree.getDecorator = function (deco) {
tree[deco].prototype = this;
return new tree[deco];
};
When this getDecorator
method is invoked for a second time, it returns an instance of tree.Angel
.
This is similar to tree.BlueBalls
, except that its prototype is tree.BlueBalls
, not our original tree
object.
console.log(tree);
tree.Angel {
decorate: function () {
__proto__: tree.BlueBalls
decorate: function () {
__proto__: Object
Angel: function () {
BlueBalls: function () {
RedBalls: function () {
decorate: function () {
getDecorator: function (deco) {
Now if we run everything again, we get:
tree = tree.getDecorator('BlueBalls');
tree = tree.getDecorator('Angel');
tree.decorate();
> Make sure the tree won't fall
Add blue balls
An angel on the top
This is because when tree.decorate()
is called, tree
is an instance of tree.Angel
.
tree.decorate
first invokes it’s prototype’s (tree.BlueBalls
) decorate
method.
tree.BlueBalls
invokes it’s prototype’s decorate
method, which logs Make sure the tree won't fall
Execution then returns to tree.BlueBalls
’s decorate
method, which logs Add blue balls
Execution finally returns to tree.Angel
’s decorate
method, which logs An angel on the top
The same thing happens again when we reassign the value of tree in the following line:
tree = tree.getDecorator('RedBalls');
I hope that helps.
I find the terminology for all of this a little tricky, so I would be grateful if anyone fancied improving my explanation.