views:

55

answers:

4
var utils = function() {
    function getMyPrivateName() {
        return "Caoimhin";
    }
    return {
        messages: {
            getMyPublicName: function getMyPublicName() {
                return "Kevin";
            },
            sayHello: function() {
                document.writeln("hello " + getMyPublicName() + "<br/>");
                document.writeln("hello " + getMyPrivateName() + "<br/>");
            }
        }
    };
} ();

utils.messages.sayHello();

I am playing around with javascript namespaces and have encountered unexpected behaviour. I develop mostly in IE as that is the target browser for our intranet application.

In IE the above, when included on a blank page, outputs:

hello Kevin
hello Caoimhin

In FF the script encounters an error:

getMyPublicName is not defined

If I comment out the offending line:

//document.writeln("hello " + getMyPublicName() + "<br/>");

FF outputs:

hello Caoimhin

So I know it can access the private function...

Can anyone explain why this is happening? And what I need to do in order to have a cross browser solution similar to the above..

I know I could write something like:

document.writeln("hello " + utils.messages.getMyPublicName() + "<br/>");

but would prefer not to....

Thanks in advance, Kevin

+2  A: 

It's a matter of scope:

var utils = function() {
    function getMyPrivateName() {
        return "Caoimhin";
    }
    return {
        messages: {
            getMyPublicName: function() {
                return "Kevin";
            },
            sayHello: function() {
                document.writeln("hello " + this.getMyPublicName() + "<br/>");
                document.writeln("hello " + getMyPrivateName() + "<br/>");
            }
        }
    };
} ();

utils.messages.sayHello();
Dustin Hansen
+4  A: 

Firefox' behaviour is conforming, as a function definition within an object initialiser is a function expression and not a function declaration.

ECMA-262, 3rd edition, section 13:

The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

I suggest moving the definition out of the object initialiser:

var utils = (function() {
    function getMyPrivateName() {
        return "Caoimhin";
    }
    function getMyPublicName() {
        return "Kevin";
    }
    return {
        messages: {
            getMyPublicName: getMyPublicName,
            sayHello: function() {
                document.writeln("hello " + getMyPublicName() + "<br/>");
                document.writeln("hello " + getMyPrivateName() + "<br/>");
            }
        }
    };
})();


utils.messages.sayHello();
Christoph
Thanks for answering so well
Kevin
A: 

One way of doing this is:

var utils = function() {
    var getMyPrivateName = function () {
        return "Caoimhin";
    };

    var self = {
        getMyPublicName: function () {
            return "Kevin";
        },
        sayHello: function() {
            document.writeln("hello " + getMyPublicName() + "<br/>");
            document.writeln("hello " + self.getMyPrivateName() + "<br/>");
        };

    return self;
}();
Bill Zeller
+5  A: 

You have stumbled across a bug in the JScript language as used by IE.

getMyPublicName: function getMyPublicName() {
    ...
},

The function getMyPublicName() value here is an inline function expression that has been given an optional identifier getMyPublicName that is the same as its property name in the owner. (sayHello omits this identifier.)

What the optional identifier should do according to the ECMAScript standard is make a reference to the function visible in the scope of the function body itself, under the identifier name getMyPublicName. This could be used for making an anonymous inline function that referred to itself (for recursion).

What it actually does in IE, incorrectly, is make the function visible under the name getMyPublicName in the parent scope (the utils function). Because it's visible in that scope it becomes visible to the child scope of the sayHello function too, making your code work when it shouldn't, really.

You can use this. to get the reference to getMyPublicName properly, as suggested by Dustin. Alternatively if you want to avoid the issues of this-binding in JavaScript (eg. because you're going to pass the sayHello function as a delegate to a timeout or event), you might prefer to put the public functions in parent scope too:

var utils = function() {
    function getMyPrivateName() {
        return "Caoimhin";
    }
    function getMyPublicName() {
        return "Kevin";
    }
    function sayHello() {
        document.writeln("hello " + getMyPublicName() + "<br/>");
        document.writeln("hello " + getMyPrivateName() + "<br/>");
    }
    return {
        messages: {
            getMyPublicName: getMyPublicName,
            sayHello: sayHello,
        }
    }
}();
bobince
Thanks for the detailed explanation, some great information here for me.
Kevin