What’s new in JavaScript (Google I/O ’19)

This is Sathya (hey everyone), my name is Mathias
and we work on the V8 team at Google. V8 is the JavaScript engine and the
WebAssembly engine that’s used in Google Chrome and in other embedders such as
Node.js and Electron. Other Chromium-based browsers such as Opera and very soon
Microsoft Edge also build on top of V8. JavaScript started out as the scripting
language of the web, and to this day it’s still a crucial piece of web technology.
But today, JavaScript’s reach extends far beyond just the web to the entire
surrounding platform. So if we take a step back from V8 and Chrome we can see
that JavaScript allows you to create engaging web apps across a wide variety of
browsers. With Node.js, JavaScript can be used to build command-line tools, servers,
and other applications. With Electron, JavaScript and other web technologies
can be used to build cross-platform desktop apps. React Native enables building
cross-platform mobile apps. JavaScript can even run on low-cost IoT devices
nowadays. JavaScript is *everywhere*, so as a developer learning JavaScript or
improving your JavaScript skills is an excellent time investment. At Google we
want to enable this enormous JavaScript ecosystem. We do so in multiple ways. TC39
is the committee that governs the evolution of ECMAScript, which is the
standard behind the JavaScript language. We actively participate in these
standardization efforts together with other browser vendors and industry
stakeholders, bringing back feedback from us as implementers and from the
developer community. On top of our participation in the standards process
we constantly improve our own JavaScript engine, V8. We boost its performance,
reduce its memory usage, and we implement new language features as they’re being
standardized. Our mission is to lead real-world performance for modern
JavaScript and WebAssembly, and to enable developers to build a faster
future web. You might notice WebAssembly is also
implemented in V8, alongside JavaScript. Our presentation today focuses just on
JavaScript features, but there’s a separate WebAssembly presentation at I/O. If
you’re interested, check out Deepti and Surma’s “WebAssembly for web developers” talk
later today. Now let’s discuss what we mean when we
say real-world performance. We’ve stepped away from synthetic benchmarks where
someone sits down and writes code specifically for benchmarking purposes.
Such code often does not reflect real-world usage and so optimizing for
these scenarios as a JavaScript engine has limited impact. Instead we now focus
on optimizing real websites and JavaScript applications. Let’s walk
through some example improvements. Since Chrome 61, we’ve doubled our raw
JavaScript parsing speed as measured on real-world websites. On top of that we’ve
managed to move up to 40% of parsing and compilation work off of the main thread
so that it no longer blocks website startup. 40%! And we’re working to
increase this percentage even further. Our efforts are not just visible in
Chrome but also in Node.js. Overall, promises got up to 11 times faster between Node.js 7
and Node.js 12. Promise.all in particular got up to 13 times faster. To put that in
Chrome version numbers, that’s Chrome 55 compared to Chrome 74. But
performance is not just about parse speed and runtime performance. Memory
consumption also plays a big role. And that’s why between Chrome 70 and Chrome
76, we worked to reduce overall memory consumption by 20% as measured on
Android Go devices running real-world web apps. So we’ve improved startup
performance by boosting JavaScript parsing speed,
we’ve improved runtime performance by optimizing async and promises, and we’ve
significantly reduced memory consumption along the way. And those are just some
examples of what we’ve been working on. Wait, is something wrong with our
slides? That does look funky… but I think we talked about this last year — in a
regular expression, this would be a lookbehind. I see. So I guess it’s time to
look… back? Alrighty then! Exactly one year ago, we were right here at Google I/O,
telling you about lots of exciting new JavaScript features. And some of those
features were pretty cutting-edge, they were still being standardized at the
time. And so unfortunately for many features we had to say that they weren’t
yet supported in all modern environments. But today, one year later, feature support
looks a lot better. In fact all the features you see here are now fully
supported across modern browsers and Node.js. You no longer have to polyfill or
transpile these features when targeting modern environments. That means you can
ship smaller JavaScript bundles so your apps load faster, and you get those juicy
performance improvements as well. On top of that, some of the other features we
talked about have received updates since last year. Let’s take a look at what’s
changed, starting off with class fields. Here’s a code example that creates an
instance of a class named IncreasingCounter. Note that accessing the `value` executes some code.
In this case it logs a message before returning the result. Now
ask yourself, how would you implement this in JavaScript? So here’s how
IncreasingCounter could be implemented using the familiar ES2015 class syntax.
The class installs the `value` getter and an `increment` method on the prototype, but
more interestingly, the class has a constructor that creates an instance
property `_count` and sets its default value to `0`. We currently use
the underscore prefix to denote that `_count` should not be used directly by the
consumers of the class, but that’s just a convention — the privacy is not enforced
by the language. Anyway, that’s what this class looks like using the old
familiar class syntax. The new public class fields syntax allows us to simplify
the class definition. The `_count` property is now nicely declared at the top of the class. We no longer need a constructor
just to define some fields. Pretty neat! But, the `_count` property is still a public
property in this particular example. We want to prevent direct access to that
property. And that’s where private fields come in. So the new private fields syntax
is similar to public fields, except you mark the fields as being private by
using the `#` symbol. You can think of the hash as being as part of the field
name. This is more than just fancy syntax: private fields are actually private! This
means they’re not accessible from outside the class body. When you try, you
get a SyntaxError. Class field syntax is especially convenient when dealing with
subclasses that introduce additional fields. Consider this `Animal` base class
for example. To create a `Cat` subclass that introduces an additional instance
property, you’d previously have to call `super()` to run the constructor of the
`Animal` base class before creating this property. That’s a lot of boilerplate
just to indicate that cats don’t like taking baths! Luckily, the class
field syntax removes the need for the whole constructor, including the awkward
`super` call. All it takes to add the class field is just that one line. Chrome
supports both public and private class fields, and so does Node.js. And more class
features are coming soon! We’re working on adding support for private methods
and private getters and setters. Another feature we previewed last year is
String#matchAll, which is useful when dealing with regular expressions. It’s common to
repeatedly apply the same regular expression on a string to get all the
matches. And to some extent this is already possible today by using the
String#match method. In this example we find all the words that consists of
hexadecimal digits only, and then we log each match. However, this only gives
you the substrings that match. Usually you don’t just want the substrings, you also
want additional information such as the index of each substring or the
capturing groups within each match. And it’s already possible to achieve that by
writing your own loop and then keeping track of the match objects yourself, but
it’s a little annoying and just not very convenient. The new String#matchAll API
makes this easier than ever before. You can now write a very simple `for-of` loop
to get all the match objects. String#matchAll is especially useful for
regular expressions with capture groups. It gives you the full information for
each individual match, including the capturing groups. The general idea is
that you just write a simple `for-of` loop, and String#matchAll takes care of
everything else for you. String#matchAll is already supported in Chrome, Firefox,
and Node.js. For other browsers, a polyfill is available. Last year we also talked
about the numeric separators proposal. Here’s a quick recap. Large numeric
literals are difficult for the human eye to parse quickly. Especially when there
are lots of repeating digits, like in the first example. To improve readability, a
new JavaScript feature enables underscores as separators in numeric
literals. So instead of writing these numbers like this, you can now write them
like this, grouping the digits per thousand. Now it’s easier to tell that
the first number is in fact a trillion and the second number is in the order of
a billion. This small feature helps improve readability for all kinds of
numeric literals. Last year, this feature wasn’t supported anywhere yet. The details of
the proposal were still being worked out. But since then, the open issues have been
resolved, and browsers can now start implementing it. Numeric separators
are already shipping in Chrome 75, and transpilers like Babel support them too. Speaking of numbers, we have some news
about BigInt as well. Here’s a quick reminder why BigInt is
useful. In this example we’re multiplying two numbers. Now, I’m no math genius, but
this doesn’t look right to me. Looking at the least significant digits, 9 and 3, we
know that the result of the multiplication should end in 7 (because 9
times 3 is 27) but instead the result ends in a bunch
of zeros. That cannot be the correct result! Now let’s try this again with
BigInts instead. And this time we do get the correct result. BigInts make it
possible to accurately perform large integer calculations in JavaScript.
Now what’s new compared to last year is that BigInt now has much better API
support within the language. For example, you can now format a BigInt in a locale-aware
manner by using the `toLocaleString` method. This now works just like
it does for regular numbers. In English for example, it’s common to use commas to
group the digits per thousand. In German, periods are used instead. And in French,
it’s spaces. If you plan on formatting multiple numbers or BigInts
using the same locale it’s more efficient to use the Intl.NumberFormat
API. That way you can create a single formatter instance, and
then just reuse that as many times as you need it. This API now supports BigInts
as well. And of course, numeric separators work with BigInt literals as well! This
means that you can now both use separators to keep your source code
readable, and produce strings with correctly formatted numbers using locale-specific
separators, all in vanilla JavaScript! I think that’s pretty cool.
Last year BigInts had just shipped in Chrome, and they were not available
anywhere else yet. But now BigInts are shipping in Firefox Nightly and in Node.js,
and an implementation is also underway in Safari. On top of that, there’s now a
transpiler and a polyfill story available for BigInts — it’s called JSBI. Now let’s talk about some new exciting
features that we’ve been working on since last year.
First up is Array#flat and flatMap. Array#flat returns a flattened version
of a given array. The array in this example is two levels deep: it contains an array
which in turn contains another array. We can call `flat` to flatten the array by
one level. The default depth is 1 but you can pass any number to recursively
flatten up to that depth. To keep flattering recursively until the result
contains no more nested arrays, we can pass `Infinity`. Here’s another example: we
have a `duplicate` function that takes a value and returns an array that contains
the value twice. If we apply `duplicate` to each value in an array, we end up with a
nested array. You can then call `flat` to flatten the array. But since this
pattern is so common in functional programming, there’s now a dedicated `flatMap`
method for it. flatMap is a little bit more efficient compared to doing a
`map` followed by a `flat` separately. Array#flat and flatMap are already
available in Chrome, Firefox, Safari, and Node.js. Another cool new addition to the
built-in JavaScript library is Object.fromEntries. You may have used the
Object.entries API before — that one has been around for a while. For each key-value pair
in an object, it gives you an array where the first element is the key,
and the second element is the value. And this is especially useful in combination
with `for-of`, because you can then very elegantly iterate over all the key-value
pairs in an object. Unfortunately, there’s no easy way to go from the
entries result back to an equivalent object — until now! The new Object.fromEntries
API performs the inverse of Object.entries. This makes it really easy
to reconstruct an object based on its entries. Now why is this useful?
Well, one common use case is transforming objects. You can now do this by iterating
over the object’s entries, and then using array methods that you are probably
already familiar with. In this example we’re filtering the object to only get
keys of length 1, that is, we only want the keys `x` and `y` but not the key `abc`. We
then map over the remaining entries and return an updated key-value pair for
each. In this example, we double the value by multiplying it by 2. And the end
result is a new object containing only the properties `x` and `y` with the new
values. JavaScript also supports maps, which are often a more suitable data
structure than regular objects. So in code that you have full control over, you
might already be using maps instead of objects. However, as a developer you don’t
always get to choose the representation: sometimes the data that you’re operating
on comes from an external API or from some library function that gives you an
object instead of a map. Object.entries made it easy to convert objects
into maps, but the inverse is equally useful! Even if your code is using maps,
you might need to serialize your data at some point, for example to turn it into
JSON to send an API request, or maybe you need to pass the data to another library
that expects an object instead of a map. In these cases you need to create an
object based on the map data. Object.fromEntries makes this trivial. With
both Object.entries and Object.fromEntries in the language, you can now
really easily convert between maps and objects, both ways. Object.fromEntries is
already shipping in Chrome, Firefox, Safari, and in Node.js. For older browser
versions, a polyfill is available. Another new feature seems like a small one but
it’s a big improvement. Here’s the problem: libraries and polyfills sometimes need access to the global `this` value. To
write universal JavaScript that works everywhere regardless of whether you’re
in a browser, in Node.js, or in a JavaScript engine shell directly, you need code like
this to access the global `this`. In browsers, you can use `window`… except in
web workers or service workers, `window` is not available, so you have to check for
`self` as well. In Node.js, you can use `global` instead, but in standalone JavaScript
shells, none of these work, so you can try falling back to whatever `this` object is
currently available. But outside of browsers, that still won’t work within
modules, since they don’t run in the global scope, or in strict mode functions
since their `this` is undefined. Also, bundlers often wrap source code, in
which case `this` might refer to something else entirely. You cannot rely on this
code being run in the global scope in all cases. So even though we’re trying
really hard for this code snippet, it still doesn’t support standalone
JavaScript engine shells, it doesn’t support modules in all environments, it
doesn’t support strict mode functions in all environments, and it might even break
in more scenarios when you start bundling it. Getting the global `this`
sounds like it would be simple, but it’s actually painful to do so in practice.
And that’s why we have a new `globalThis` feature that gives you easy access to
the global `this`, regardless of the environment. `globalThis` is already
supported in Chrome, Firefox, Safari, and Node.js. Note that most modern code won’t
need access to the global `this` — with JavaScript modules you can decoratively
import and export functionality without messing with global state. `globalThis` is
still useful for polyfills and other libraries that need global access. Stable
sort is another one of those changes that sounds like a small improvement, but
it actually has a big impact. Let’s say you have an array of
dogs and each dog has a name and a rating. If this sounds like a weird
example, you should know that there is a Twitter account that specializes in
exactly that (don’t ask). Anyway, the array is pre-sorted
alphabetically by name, but what if you want to sort this array by rating so
that the highest rated dogs show up first? You would use Array#sort for this,
and you would pass in a custom callback that compares the ratings. And this is
the result that you would probably expect. The dogs are now sorted by rating,
but within each rating they’re still sorted alphabetically by name — they
retain their relative original order. For example Choco and Ghost have the same
rating of 14, but Choco appears before Ghost in the sort result, because that’s
the order they had in the original array as well. To get this result however, you
cannot just use any sorting algorithm — it has to be a so-called stable sort. And
for a long time, the JavaScript spec didn’t require sort stability for Array#sort.
Instead, it left it up to the implementation and because this behavior
wasn’t specified you could also get this sort result, where Ghost now suddenly
appears before Choco. In other words, JavaScript developers
could not rely on sort stability. And in practice, it was even more confusing than
that, because some JavaScript engines would use a stable sort for short arrays
but an unstable sort for larger arrays. That was really confusing because
developers would test their code, they would see a stable result, but then
suddenly get a completely unstable result when the array was slightly
bigger. But there’s some good news: we proposed a spec change that makes
Array#sort stable, and it was accepted. All major JavaScript engines now
implement a stable array sort. It’s just one less thing for you as a JavaScript
developer to worry about. V8 also implements several Intl APIs that
provide key locale-specific functionality, such as date formatting,
number formatting, plural form selection, and collation in JavaScript.
These APIs allow you to localize your websites without having to ship
hundreds of kilobytes of locale data to the user. Let’s look at some of the new
features we’ve added recently. Modern web applications often use phrases like
“yesterday”, “42 seconds ago”, or “in three quarters” instead of full dates or
timestamps. The Intl.RelativeTimeFormat API can generate these phrases for you
for each language you support. This code example creates a relative time formatter
for the English language. We set the `numeric` option to `’auto’` to tell the
formatter it can use phrases like “yesterday” instead of “one day ago”. The
formatter recognizes several time units such as seconds, minutes, hours, days,
months, quarters, and weeks. We pass it a number and a unit, and it returns a
string that represents the relative time. And this doesn’t just work for English,
but for other languages like Tamil as well.
Previously developers had no choice but to implement such APIs themselves. One
problem with implementing a localized relative time formatter is that you need a
list of customary words or phrases such as “yesterday” or “last quarter” for each
language you support. The Unicode CLDR provides this data, but you use it in
JavaScript, it has to be shipped alongside other library code. This
unfortunately increases the bundle size for such libraries, which negatively
impacts load time, parse/compile time, and memory consumption. With the new
Intl.RelativeTimeFormat API, the browser ships the data, so you get the
functionality without paying the cost. The RelativeTimeFormat API is
available in Chrome, Firefox, and Node.js. Modern web applications often use lists
consisting of dynamic data, and they require locale-specific formatting
of such lists. For example a photo viewer app might display a message like “this
photo includes person A, B, and C”. The Intl.ListFormat API can help you
format such strings for any given language using the locale-specific list
conventions. A text-based game might have a different kind of list: it could say
something like “choose your favorite hero: X, Y, or Z”. The ListFormat API supports that
too, using the `disjunction` type. Because each language has different list
formatting conventions and words, implementing a localized list formatter
in JavaScript is non-trivial. Not only does this require a list of all the
words such as “and” or “or” for each language you want to support — in addition
you need to encode the exact formatting conventions for all those languages. The
Intl.ListFormat API takes care of all of that for you. The ListFormat proposal
is available in Chrome and Node.js. For other browsers, a polyfill is available. It’s common for websites to display date
intervals or date ranges to show the span of an event, such as a hotel
reservation, the billing period of a service, or I don’t know, a big tech developer
festival in Mountain View. The `formatRange` method provides a convenient way of
formatting ranges in a locale-specific manner. In the above example, we create
two dates and print the date range using `DateTimeFormat`. Intl.DateTimeFormat is an
existing API that has been shipping in all browsers for a while. One way to print
the date range is to use the regular locale-unaware string formatting: you
format the start date, you format the end date, and piece them together into a
string. Here the second date’s month and year calendar fields are redundant — only
the day provides new information. So instead, with the new `formatRange` method, we can
display all the relevant fields and none of the
repeating ones. The `formatRange` method is available behind a flag in Chrome, and
we plan to ship it very soon. In the previous examples, you may have noticed the
locale ID that we passed to the various Intl constructors, such as `’en’` for
English. Intl.Locale offers a powerful mechanism to deal with such locales. It
enables easily extracting locale-specific preferences, such as not only
the language, but also the calendar, the numbering system, the hour cycle, the
region, and so on. The Intl.Locale proposal is now available in Chrome and
Node.js. You can use a polyfill for other browsers.
If you’ve used promises before, you might already be familiar with the lovely
async/await syntax. Instead of chaining promise callbacks using `then`, you can
nicely `await` the result of a promise. This syntax has been around for a while.
However, `await` can only occur within an async function. There are use cases
for wanting to use `await` at the top level, but currently this isn’t
possible. One workaround is taking all your top-
level code and wrapping it up in an async function that you then call. Another
approach is to wrap your top-level code in an immediately-invoked async function
expression. But a new proposal enables the use of `await` at the top-level,
removing the need for these workarounds. The Chrome DevTools Console already
wraps your input in an async function behind the scenes if the input contains
`await`, so it looks like top-level `await` already works in the console. But that’s
not because we implement the proposal — we don’t! The exact details of the top-level
`await` proposal are still being worked out, and as such it’s still too early to
implement it. It’s not currently supported everywhere. Now that we’re on the subject of
promises and async code, let’s talk about promise APIs. Currently there are two
promise combinators in JavaScript: the static methods `all` and `race`. Promise.all
lets you know when either all input promises have fulfilled, or when one of
them rejects. Promise.race lets you know as soon as one of the input promises
settles, which means it either fulfills or rejects. These are independently useful, and have
different use cases. Imagine the user clicks a button and you want to
load some stylesheets you can render a completely new UI. This program kicks off
an HTTP request for each stylesheet in parallel. Then you only want to start
rendering the new UI after all the requests succeeded, but if something goes
wrong you want to instead display an error message as soon as possible,
without waiting for any other work to finish. In such a case, you could use
Promise.all: you want to know when all promises are fulfilled, or as soon as
one of them rejects. Promise.race is useful if you want to run multiple
promises and either do something with the first successful result that comes
in, in case one of the promises fulfills, or do something as soon as one of the
promise rejects. That is: if one of the promises rejects, you want to preserve
that rejection, and treat that error case separately. Here, we do exactly that:
we kick off a computationally expensive task that might take a long time, but
we race it against a promise that rejects after two seconds. Depending on
the first promise to fulfill or reject, we either render the computer result, or
the error message in two separate code paths. There was a top-level await in
that example, by the way. So those are the existing promise combinators: `all`, and
`race`. Two new proposals are currently making their way through the standardization
process: Promise.allSettled, and Promise.any. With these additions there
will be a total of four promise combinators in JavaScript, each
enabling different use cases. Promise.allSettled gives you a signal when all of
the input promises are settled, and that means they’re either fulfilled or
rejected. This is useful in those cases where you don’t really care about the
state of the promise — you just want to know when the work is done, regardless of
whether it was successful. For example, you can kick off a series of independent
API calls and then use Promise.allSettled to make sure that they’re all completed
before doing something else, like removing a loading spinner. Promise.any
gives you a signal as soon as one of the promises fulfills. this is similar to
Promise.race, except `any` doesn’t reject early when one of the promises rejects.
This code example checks which endpoint responds the fastest, and then logs it.
Only if all of the requests fail, do we end up in the `catch` block where we can
then handle the errors. Promise.any is still in the early stages of being
standardized, and it’s not supported anywhere yet. Promise.allSettled on
the other hand is already available in Chrome 76 and in Firefox Nightly. For
other browsers, a polyfill is available. Next up are weak references. Generally
references to objects are strongly held in JavaScript, meaning that as long as
you have a reference to an object, that object won’t be garbage-collected.
Currently WeakMaps and WeakSets are the only way to weakly reference an object.
Adding an object to a WeakMap or a WeakSet doesn’t prevent it from being
garbage-collected. Weak references are a more advanced API that provide a window
into the lifetime of an object. So here we have a `getImage` function that takes
a name and performs some expensive operation to generate another object,
like a binary blob of image data. To improve performance, we store the image
in a cache. Now we don’t have to perform the expensive
operation again for the same name. But there’s a problem: maps hold on to their keys and values strongly, so the image names and data can
never be garbage-collected so this steadily increases memory and
causes a memory leak. Note that using a WeakMap would not help here, as WeakMaps
don’t support strings as keys. A new proposal makes it possible to solve the
memory leak by creating a weak reference to the image and storing that in the
cache instead of the image itself, so when the garbage collector realizes that
it’s running out of memory, it can clean up some of the images. But there’s still
a problem here: the map still holds on to the name strings forever, because those
are the keys in the cache. Ideally those strings would be removed as well. The
WeakRefs proposal has a solution for that as well. With the new `finalizationGroup`
API, we can register a callback to run when the garbage collector zaps a
registered object. So here, we register a callback to remove the keys from the
cache when the image objects are garbage- collected. Our final implementation
looks like this: given an image name, we look up its corresponding weak reference
in the cache. If the weak reference still points to something, we can return the
cached image data. If there’s no cache entry yet, or if the cached data has
already been garbage-collected, we compute the image data, create a new weak
reference to it, store the image name and weak reference in the cache, register a
finalizer that removes the image name from the
cache once the image data is garbage- collected, and return the image. The
WeakRefs proposal is still going through the standardization process. We
have an experimental implementation in V8 behind a flag already, but we won’t
ship it until the proposal is finished. We’ve
talked about lots of amazing JavaScript features today with for varying degrees
of support. We’re very excited to see how much the JavaScript landscape has
evolved over the past year, and we look forward to increased support and
adoption of these new features across the entire ecosystem. And who knows maybe
next year, we’ll be back here at Google I/O
announcing that all these cutting-edge features are now supported everywhere.
Wouldn’t that be nice! Fingers crossed. If you have any feedback
at all on this session, or have any questions about modern JavaScript, then
don’t hesitate to let us know on Twitter. Thanks for listening and enjoy the rest
of I/O. Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *