Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Randy Brown
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Randy Brown

Building A Mental Model For Precedence And The "new" Operator In JavaScript

By
Published in Comments (15)

For the most part, when I write JavaScript code, I try to be mindful. Which means that when I write JavaScript code - or any code for that matter - I never want to rely on operator precedence. This is for me; but, it's just as much for the next engineer who has to maintain my code. As such, I am very liberal with my use of parenthesis so as to add clarity to the way in which an expression will be evaluated. That said, the one case in which I do tend to lean on operator precedence is when using the "new" operator for constructing object instances. But, the reality is, I've never felt quite comfortable with this. The "new" operator precedence has always felt a little bit like black magic. So, I wanted to take a minute to and try to build a better mental model around it.

If you look at the Mozilla documentation on Operator Precedence, you can see that the "new" operator has the same precedence - 19 - as member access and function calls.


 
 
 

 
JavaScript opreator precedence from Mozilla Developer Network (MDN).  
 
 
 

The one place where this concept of precedence and the "new" operator keeps coming up for me is when I create a new Date object in order to access the current UTC tick count:

new Date().getTime()

NOTE: In modern browsers, and Node.js, you can simply use Date.now().

How does JavaScript know that the Date object is a constructor? How does JavaScript know that Date() isn't just a function call that returns an object that contains a constructor called GetTime()? After all, the constructor can certainly live as an object property:

new global.Date().getTime()

If member access and function calls all have the same operator precedence - 19 - how does JavaScript know when to stop evaluating the right-hand side of the expression and instantiate a new object?

I think the trick here is that the associativity for the "new" operator is "N/A" (not applicable). Which, in my mind, means that the "new" operator is actually a little bit "black magic."


 
 
 

 
Shia Labeouf - magic.  
 
 
 

The mental model that seems to work best for me (so far) is that JavaScript will evaluate the operators (in group 19) from left-to-right until it hits a Function with arguments (ie, using parenthesis). At that point, JavaScript will apply the "new" operator and then continuing evaluating the rest of the expression on the result of the instantiation. As such, we can think of the expression:

new global.Date().getTime()

... as being equivalent to:

( new global.Date() ).getTime()

JavaScript found the first Function invocation in the expression, stopped to apply the "new" operator, and then continued on, evaluating .getTime() on the result of the instantiation.

Now, going back to my earlier question on returning a constructor from a function, how would JavaScript know to evaluate something like this:

new echoValue( Date )().getTime()

... where echoValue() is a function that returns its first argument?

Well, it won't. JavaScript will end up applying the "new" operator to the echoValue() Function since it's the first Function with arguments in the expression. If we really needed to return a constructor from a Function, we would have to explicitly give the function call higher precedence. And, in JavaScript, the only thing with higher precedence is the Grouping operator:

new (echoValue( Date ))().getTime()

As you can see, we've wrapped our echoValue() call in parenthesis in order to indicate that it has a higher precedence - 20 - than the "new" operator - 19. Now, the echoValue() Function will be evaluated first and echo back the Date object, which becomes the left-most newable object in the expression.

This might look really silly; but, one place this has practical implications is when you need to access a constructor returned by a require() call in Node.js:

new (require( "my-module" )).MyModule();

In order to prevent JavaScript from applying the "new" operator to the require() Function, we can wrap it in parenthesis. This tells JavaScript that the require() call has a higher precedence than the "new" operator; which makes the MyModule Function the first newable Function in the evaluated expression.

AN ASIDE ON TEAMWORK: Parenthesis are your friend. Operator precedence is an interesting topic; but, you should absolutely never assume that it is second nature to your teammates. Your 6th grade Algebra teacher may have demanded that you know that multiplication comes before addition (remember PEMDAS); but, when you're working with a team, never never ever depend on operator precedence! Always use parenthesis to spell out exactly what you are expecting to happen.

The "new" operator is a tricky operator. And to be clear, this post is basically me guessing at how and why JavaScript is evaluating expressions that contain a mix of different operators with the same precedence. That said, this mental model is working well for me so far. And, hopefully, it works well for you in case you also had a less-than-stellar understanding of when an expression is "newed" into existence.

Reader Comments

1 Comments

This was a great article, Ben. I'm using Javascript for almost 4 years and the idea of how JS knows a function has a constructor never crossed my mind.
Now it's all cristal clear.

15,811 Comments

@Derek,

Kyle Simpson is an awesome resource! I keep meaning to read his books. He always forces me to think more deeply about how things work and about what kind of assumptions I'm making.

3 Comments

Your comments at the beginning and end of this article, regarding liberal use of parens for clarity and readability, echo statements I've made to my own team numerous times. Even though certain statements make for perfectly legal assignments according to the language itself, parens are free, will be removed during minification/obfuscation, and greatly aid in readability and signaling the actual intent of how a statement should execute, both to other engineers, and to future you, when you have to come back and maintain it 6+ months from now :) I was glad to see that mentality validated here :)

1 Comments

"until it hits a Function with arguments (ie, using parenthesis)."

Huh? Your example of "Date()" uses parentheses but doesn't include any arguments. The part of MDN you quoted said "argument list", where I guess a "list" could be empty, but "with arguments" implies a non-empty argument list.

15,811 Comments

@Richard,

It's like my Dad used to say me when crossing the street - always let the cars go. With regard to "rightive way", he always said, "You might be right -- dead right" (indicating that I might be right to cross, but the car will still kill me).

Ok, maybe not a great analogy - but the point is, doing "good" for your team is not the same as being "right" in the code.

3 Comments

@Ben,

I'm not sure how to interpret that :D Do you mean sometimes being "dead right" in the code can be bad for the team if it's done too militantly? Or too nitpicky? If so, I would agree, and always try to phrase it in a constructive fashion (or make my biggest nitpicks enforceable via linting so that I don't have to have that conversation in the first place). On some occasions, if there have been a few back-and-forths in a code review, with subsequent commits to clean things up, sometimes I'll let the lesser items go, and fix them myself at a later time, rather than seem like I'm trying to beat them down with review comments over and over :)

If you meant something else, I missed it :)

15,811 Comments

@Nathan,

Good catch - I think I miswrote there. I think what I mean to write was a "function with parenthesis". I was trying to differentiate accessing a function as an object as opposed to invoking it. For example, if "Foo" is a constructor, you can certainly store properties on that constructor (since it inherits from Object):

new window.Foo.thing();

In this case, "Foo" is a function, but it has no parenthesis - it's being accessed an object. Which is different than:

new window.Foo().thing();

... in which it is being invoked.

Good catch though, I think I was caught up in trying to thinking about it that I didn't get the right words down on paper.

15,811 Comments

@Richard,

Yeah - that's what I meant. Just wasn't getting the right words down. Just saying your code can be "right", but it doesn't mean it's "right for the team."

2 Comments

It's not magic.
Level 19 new operator syntax: new <sub-expression-1> ( <sub-expression-2> )
The sub-expression-2 can be anything.
The sub-expression-1 can be level 19-20 operators (chain), because the precedence, and a result must be an object with a constructor. But the syntax of the function call matching with the new operator syntax ending. Resulting three cases:

  • If the result - before the first backet - is a constructor, the expression ends,
  • if it's a function, the expression continue,
  • and if both, the function call win.
    And, the bracket and the new operator must be first in a subexpession, because they are non-associative, not "chainable" to left-to-right.
15,811 Comments

@Mlaci,

But, how is the JavaScript engine differentiating between a "constructor" and a "function". After all, can't all Functions essentially be used as a constructor? Even an object method can be used as a constructor if its invoked with the new operator.

This might be what you are saying -- I think maybe I am not fully understanding your explanation. But, given my confusion about the topic at its onset, this is not surprising :D

2 Comments

@Ben,

Sorry, my answer misleading, it's nothing to do with types, it just how to parse the javascript syntax to syntax tree.
I wrote incorrectly that <sub-expression-1> could contain function calls, I messed up something in a console when i tested. Instead, try some example from your blog post on https://astexplorer.net, it's much better tool for it.

15,811 Comments

@Mlaci,

That site looks really interesting. I know of AST (Abstract Syntax Tree); but, I've never really thought much about it, other than it probably makes for better work-flows than parsing via Regular Expressions :D

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel