Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Dave Ferguson
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Dave Ferguson@dfgrumpy )

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

By Ben Nadel on

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

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.

Reply to this Comment

@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.

Reply to this Comment

I wish the YDKJS series and Eloquent JS existed when I started JS, both are just insanely great resources.

Reply to this Comment

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 :)

Reply to this Comment

"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.

Reply to this Comment

@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.

Reply to this Comment

@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 :)

Reply to this Comment

@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.

Reply to this Comment

@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."

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.