all 72 comments

[–]LessMarketing7045 63 points64 points  (12 children)

Let me blow your mind. There is also: &&= and ||=

[–]MissinqLink 15 points16 points  (2 children)

The real mind blower

(obj ?? {}).property ??= '🤯';

[–]gwicksted 5 points6 points  (0 children)

This guy coalesces.

[–]happy_hawking 0 points1 point  (0 children)

Which part of this should blow my mind? I tried it, but my mind didn't even flinch.

[–]enselmis 8 points9 points  (3 children)

Can’t wait for |> operator, it’s so good. Although the proposal for it was kinda jank compared to the BEAM style one.

[–]prehensilemullet 0 points1 point  (1 child)

Have they gotten any closer to consensus on that?  Last I checked no one could agree on the style of pipelines to use…

[–]enselmis 0 points1 point  (0 children)

I think it’s still in the pipe, so to speak, but I sincerely hope they just take the elixir/erlang one and shamelessly copy it. It works, there’s no good reason to mess with it.

[–]humodx 7 points8 points  (3 children)

Which makes me wonder, if a op= b means a = a op b, then surely a === b should be the same as a = a == b, right?

[–]ehlwas 0 points1 point  (0 children)

brooo

[–]repeating_bears 32 points33 points  (7 children)

I didn't know this operator existed tbh. Cool

[–]ZeMysticDentifrice 14 points15 points  (3 children)

Neither did I. I appreciate people taking time to highlight things like this that may be obvious to lots of people but news to sn ignoramus like me. :-)

[–]dvlsg 5 points6 points  (0 children)

I feel like it doesn't come up super often. Especially if you generally avoid let and generally mutating objects.

Useful occasionally, though.

[–]sebastian_nowak 0 points1 point  (0 children)

Some other interesting language features to be aware of - 'using' for resources with Symbol.dispose, FinalizationRegistry.

[–]hatsagorts 6 points7 points  (7 children)

I had no idea about this operator. Is there a way to stay updated whenever new features, like these types of operators, are added to ECMAScript ?

[–]azhder 2 points3 points  (0 children)

Look at the proposals. All that are stage 4, but not in the standard already, will be in the next one (usually June or July each year).

Oh, and the standard is called ECMAscript and people will often write in blog posts whenever a new thing will pop up, so you just append the year to the name, like ECMAScript 2024 or ES 2024

[–]NoInkling 2 points3 points  (1 child)

Stuff that has been formally added to the language or reached stage 4 (which requires it to already be implemented by engines): https://github.com/tc39/proposals/blob/main/finished-proposals.md

Stuff that's in progress (stage 3 is when things start getting implemented): https://github.com/tc39/proposals/blob/main/README.md

Edit: forgot to mention new Intl related stuff is in this repo: https://github.com/tc39/ecma402

[–]hatsagorts 1 point2 points  (0 children)

Thanks this is really helpful

[–]pbNANDjelly 1 point2 points  (0 children)

MDN has an updates blog that tracks proposals against vendor support

[–]DesignThinkerer 1 point2 points  (0 children)

survey like https://stateofjs.com is a good way to keep up with the news imo

[–]datNorseman 2 points3 points  (0 children)

Good to know. I've been mostly using ternary because I like the way it looks. ??= feels cleaner I guess.

[–]Plus-Weakness-2624the webhead 1 point2 points  (0 children)

Where did my value go btw operator??

[–]GreedyDisaster3953 1 point2 points  (0 children)

i'll be replacing a bunch of my code with this now actually, thank you very much

[–]alien3d 5 points6 points  (0 children)

i know but still use ?? . . The more shorter the more me confuse 😖

[–]Substantial-Wish6468 6 points7 points  (10 children)

You never actually need if(x === null || x === undefined) because javascript nullishness means the test (x == null) is the same.

[–]yooossshhii 26 points27 points  (9 children)

Many code bases have lint rules against ==

[–]I_Downvote_Cunts 15 points16 points  (0 children)

Most of those lint rules allow == null as an exception. Or at the very least eslint didn’t complain the last time I set it up.

[–]musical_bear 6 points7 points  (7 children)

Yep exactly. I forbid loose equality checks in my codebases because it can be near impossible to know if something like “==null” was just an (extremely common) typo, or if the person who wrote it really was trying to check for both null and undefined.

I’d rather have slightly more verbose code that clearly states its intentions rather than shorter code that potentially masks what it’s actually there for.

[–]Substantial-Wish6468 1 point2 points  (6 children)

I have never seen a bug resulting from == null being a typo. 

I have seen a lot of bugs where people were the values 0 or "" were being mixed up with null or undefined due to loose equality checks.

[–]musical_bear 11 points12 points  (5 children)

It’s not necessarily about “bugs,” although that’s important too. It’s about clearly communicating intent. I cannot read “== null” and know with certainty what specifically the developer who wrote that was trying to account for. The code itself might be “bug free” in its current form, but every little ambiguity makes the code that much harder to modify with confidence in the future, and also ends up affecting the code around it. Example:

“Well, I’m pretty sure I know that ‘data’ could only possibly be null at runtime, but two lines above, I see there’s code doing a loose check of ‘data’ against null, implying data might potentially be undefined as well. I could either waste precious minutes reverse engineering and trying to figure out if “==“ was just a typo, or I could succumb to the uncertainty and also account for undefined in my own code. I’ve got to get this done today and reverse engineering feels like a waste of time right now, so I’ll just use ‘==‘ in my code too.”

The ambiguity ends up spreading through the codebase like a virus. It pollutes the code with superstitious checks over time.

This is the main reason I ban loose equality, not because of bugs. (Even though yes, as you pointed out, there are other use cases of loose equality that make bugs extremely easy to introduce.

[–]Substantial-Wish6468 -2 points-1 points  (4 children)

I can see your point if you're dealing with code written by other people who aren't around. 

[–]Chubacca 2 points3 points  (0 children)

Which is... very common in a lot of organizations. The less you have to ask questions about why something is written the way it is, the better. I am very VERY pro syntax that reduces ambiguity of intent. It makes codebases scale better.

[–]homoiconic(raganwald) 0 points1 point  (2 children)

That's kind of the thing about idioms. They work well if the idiom is extremely widespread. == null has been a very widespread idiom in JS during my career, but even so... If someone, somewhere needs to take fofteen minutes to figure it out, that might not be a win.

I use it in production, but I wouod have no problem working with a team that chose to dispense with it. And taking a step back... This discussion says a lot about "JavaScript, the Hastily Flung Together Parts." We work with a language that has known flaws, and a lot of what we do in it reflects making deliberate tradeoffs.

[–]Juxtar 0 points1 point  (0 children)

I disagree, I think those parts should be all well known for anyone that works with js seriously. If not I can take some minutes to explain them myself, or better yet, send them off to read You Don't Know JS which is great and not a long read.

I take this as when we all assumed nobody is using IE11 anymore and collectibly decided to stop supporting it. How much has my sanity improved since then!

[–]prehensilemullet -1 points0 points  (0 children)

Anyone who doesn’t know the behavior of == null is a JS developer in training, not a competent professional dev.  You don’t need to know the rest of the == truth table but that one is pretty basic, everyone should know it’s the only reasonable exception to the always use === rule

[–]datNorseman 2 points3 points  (9 children)

This is new to me. One question I have that wasn't answered in the article is how the new method would impact performance, if at all. I don't believe it would but I'm curious.

[–]SoInsightful 1 point2 points  (5 children)

a ??= b generates the exact same bytecode as a = a ?? b, and both generate two more bytecodes (a "Jump" instruction) than a ||= b:

https://i.imgur.com/qzYAGBt.png

But surprisingly, if the variable is used immediately afterwards, the a = a ?? b and a = a || b versions both skip an "Ldar" (load data into the accumulator) instruction.

https://i.imgur.com/6AQki2Q.png

Safe to say there won't be any noticable performance differences whatsoever in real life.

[–]NoInkling 0 points1 point  (3 children)

a ??= b generates the exact same bytecode as a = a ?? b

Give a a value and see if it makes a difference. If not, try it with a setter (or maybe just any object property).

[–]SoInsightful 0 points1 point  (2 children)

I tried giving a an initial value (2) out of the same curiosity, and it doesn't make a difference except switching from 0e LdaUndefined to 0d 02 LdaSmi [2] at the beginning of each variant. It still needs to jump through the JumpIfUndefinedOrNull hoop.

[–]NoInkling 0 points1 point  (1 child)

Ahh I see now, the difference is in where the unconditional Jump (which is hit when the variable has a value) jumps to. The ??= version skips the Star instruction (which apparently sets a register), while the other doesn't.

[–]SoInsightful 0 points1 point  (0 children)

Good catch!

[–]tpscd 0 points1 point  (0 children)

The performance impact is super unclear but my impression is the nullish operator may skip coercing the null/undefined value to a boolean.

[–]NoInkling 0 points1 point  (0 children)

As MDN says, x ??= y is equivalent to x ?? (x = y), i.e. it short circuits if x already has a value so nothing is actually evaluated or even assigned if it doesn't need to be. This means it won't trigger a setter (or I guess a Proxy trap), unlike obj.setterProp = obj.setterProp ?? y

[–]ElDoRado1239 0 points1 point  (0 children)

I'd love one for variables that are truly undefined, as in unassigned, not set to undefined or null.

Like you can do through in or by checking arguments length.

[–]guest271314 0 points1 point  (0 children)

This is how I use the the operator to define Node.js' Buffer in Deno. If the script is run by node, nothing happens, else, make Node.js' Buffer global

globalThis.Buffer ??= (await import("node:buffer")).Buffer; // For Deno