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

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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!
Info
Channel: Google Chrome Developers
Views: 215,727
Rating: 4.9085112 out of 5
Keywords: type: Conference Talk (Full production);
Id: c0oy0vQKEZE
Channel Id: undefined
Length: 36min 33sec (2193 seconds)
Published: Wed May 08 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.