Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript undefined class function when missing semicolon

Tags:

javascript

function Foo() {
    var that = this;
    that.bar = function() {}
    that.baz = function() {}

    (function() {
        that.baz();
    }());
}
new Foo;

Uncaught TypeError: Object #<Foo> has no method 'baz'

that.bar works fine, it's only the last function that doesn't exist. Adding a ; after the baz function definition fixes everything.

I know excluding ; can mess some things up, but I thought for sure you're not supposed to put ; after functions. No language does that. Why does excluding the ; after the baz function cause this to break? Should I be putting ; after my function definitions?

like image 206
Farzher Avatar asked Dec 06 '22 07:12

Farzher


1 Answers

Contrary to Michael Geary's answer, assignment statements do not require semicolons.

Consider the humble immediately-invoked function expression:

(function() { /* do something */ })();

What are the extra parentheses wrapping the function() { ... } for? They're to prevent it from being interpreted as a function definition, forcing it to be interpreted as a function expression. It's basically equivalent to this, but without creating another variable:

var temp = function() { /* do something */ };
temp();

But parentheses are only one way to introduce something that must be an expression. In particular, an initializer of an assignment statement will introduce an expression1:

someVar = /* what goes here must be an expression */;

Clearly, composing these concepts shows that in this:

someVar = (function() { return 5; })();

The wrapping parentheses are not actually needed! We could just as easily write this because the function() cannot be interpreted as a function definition:

someVar = function() { return 5; } ();

So what does this have to do with semicolons? It turns out that if the next line can continue the statement or expression, it will.2 You've written this:

that.baz = function() {}

(function() {
    that.baz();
}());

With this knowledge, you know it's actually being interpreted as this:

that.baz = (function() {})(function() {
    that.baz();
}());

That's a bit tricky, so here's what's happening:

  1. First, function() {} is evaluated (not executed), yielding a function object.
  2. This function object is then called, but wait! It has some arguments we have to evaluate before we can begin executing it proper.
  3. The only argument is the expression

    function() {
        that.baz();
    }()
    

    That's obviously an immediately-invoked function expression, so we'll begin executing it.

  4. We try to find that.baz and call it, but it fails because that.baz does not exist and thus resolves to undefined! Oh no! The JavaScript engine stops here because an uncaught exception was thrown, but for clarity, let's pretend it went on.
  5. We never returned anything from function() { that.baz(); }, so the argument to function() {} is undefined.
  6. We now execute function() {} proper. It never binds its argument, so the undefined is ignored. It doesn't return anything, so the call results in undefined.
  7. Now we're done evaluating that expression! We'll set that.baz to the result, undefined.

Hopefully you see now that the parser misinterpreted your immediately-invoked function expression as, well, an argument to another anonymous function, immediately invoking it.

How to prevent it?

As mentioned previously, parentheses are not the only way to unambiguously introduce an expression. In fact, when they're in the middle of the expression unintentionally, it can cause problems like this. We also mentioned assignments, but we don't want to assign something.

What's left? Unary operators. There's a few we can use. I like !, so we'll use that. Rather than using:

(function() {
    that.baz();
}());

We use:

!function() {
    that.baz();
}();

The ! can only come at the start of an expression, so JavaScript starts parsing an expression. Only an expression can come after !, so the function is parsed as a function expression rather than a function definition. Because of precedence, the function is called first. It doesn't return anything, so it returns undefined. undefined is falsy, so ! flips it and yields true. Since we never did anything with the result of this expression, the true is discarded. (It wouldn't have mattered if we returned a truthy value either; then ! would have yielded false and that would be discarded. No matter what, it'll be discarded.)

TLDR: Use a unary operator rather than parentheses for IIFEs.


Footnotes

1Technically, assignment is an expression, not a statement, but for our purposes it does not matter.
2 With the exception of return, continue, break, and similar statements where it would confuse much more confusion.

like image 163
icktoofay Avatar answered Feb 23 '23 17:02

icktoofay