the state of javascript
hi, i’m domenic
Google Chrome
TC39
WHATWG HTML and Streams Standards
Promises
jsdom
let’s talk about
> where we’ve come from
> javascript today
> new platform innovations
> where we’re going
where we’ve
come from
history
1995: form validation, image rollovers
1997: ECMA-262 edition 1, Internet Explorer 4, DHTML
1999: ES3 (function expressions, try/catch/finally, regexps, …)
2005: Ajax
2006: jQuery
2008: V8 (the speed race begins), JS: The Good Parts
2009: ES5, Node.js, PhoneGap, JSConf, ServerJS/CommonJS
2010: Backbone.js, RequireJS
2012: Windows 8, Nodecopter
2013: Nodebots, next-gen MVC, Extensible Web, asm.js
2015: io.js, ES2015, MS Edge, Node.js 4, web assembly
javascript
today
es2015 is here
es2015: syntax
Class sugar: class, extends, super
Arrow functions: arr.map(x => x * x)
Destructuring: var { x, y } = getPoint()
Rest/spread: var [first, …rest] = els; Math.max(...myArray)
Parameter defaults: function parseInt(x, base = 10) { }
Block scoping: { let x = 5; const y = 10; }
es2015: data structures
Map: object-to-object O(1) access time
Set: O(1) lookup and storage
WeakMap/WeakSet: private state and branding
Iteration protocol: for-of across anything, even user iterables
es2015: game-changers
Generators: lazy sequences and async/await-like syntax
Promises: standardized async; play well with generators
Proxies: virtual objects, no more .get(…)/.set(…, …)!
Template strings: jsx`<a href="${url}">${text}</a>`
Subclassable built-ins: class Elements extends Array { … }
es2015 in the wild
new code styles
{
"no-var": 2,
"object-shorthand": 2,
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-template": 2
}
https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/document-type.js
"use strict";
const NODE_TYPE = require("../living/node-type");
const privates = Symbol("DocumentType internal sl
module.exports = core => {
core.DocumentType = class DocumentType extends
constructor(secret, ownerDocument, name, publ
if (secret !== privates) {
throw new TypeError("Invalid constructor"
}
super(ownerDocument);
this[privates] = { name, publicId, systemId
}
get name() {
return this[privates].name;
}
⋮
promises everywhere
generators for async
https://github.com/domenic/wattsi-server/blob/master/lib/app.js
const outDirectory = randomDirectoryName();
yield mkdirp(outDirectory);
try {
try {
const result = yield execFile(config.watt
const outputFile = path.join(outDirectory
yield fs.writeFile(outputFile, `${result.
} catch (e) {
if (e.stdout) e.message = `${e.stdout}${e
this.throw(e, 400);
}
const zipFilePath = `${outDirectory}.zip`;
yield execFile("7za", ["a", "-tzip", "-r",
this.type = "application/zip";
this.body = fs.createReadStream(zipFilePath
} finally {
yield rimraf(outDirectory);
}
transpile for browsers
and nonstandard features
https://github.com/unicorn-standard/react-callback-register
@callbackRegister
class MyComponent extends React.Component
static propTypes = {
onClick: React.PropTypes.func
}
@callbackRegister.on('click')
descriptiveName() {
// Do your stuff
}
render() {
return <div {...this.callbacks} />
}
}
new platform
innovations
#extendthewebforward
extensiblewebmanifesto.org
class CustomImage extends HTMLElement {
constructor(src) {
super();
if (src !== undefined) this.src = src;
}
get src() {
return (new URL(this.getAttribute("src"), this.baseURI)).href;
}
set src(value) {
this.setAttribute("src", value);
}
[Element.attributeChanged](name) {
if (name === "src") updateShadowDOM(this, this.src);
}
}
document.registerElement("custom-image", CustomImage);
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Constructor-Dmitry.md
custom elements
https://www.npmjs.com/search?q=service+worker
toolbox.precache(['/index.html', '/site.css', '/images/logo.png']);
toolbox.cache('/data/2014/posts.json');
toolbox.uncache('/data/2013/posts.json');
toolbox.router.get('/:foo/index.html', (req, values) => {
return new Response(
`Handled a request for ${req.url}
with :foo as ${values.foo}`);
});
toolbox.router.get('/myapp/a', toolbox.networkFirst);
toolbox.router.get('/myapp/b', toolbox.networkOnly);
toolbox.router.get('/myapp/c', toolbox.cacheFirst);
toolbox.router.get('/myapp/:page', toolbox.cacheOnly);
toolbox.router.get('/(.*)', toolbox.fastest);
https://github.com/GoogleChrome/sw-toolbox
service workers
registerPaint('circle', class {
static get inputProperties() { return ['--circle-color']; }
paint(ctx, geom, properties) {
// Change the fill color.
ctx.fillStyle = properties.get('--circle-color');
// Determine the center point and radius.
const x = geom.width / 2;
const y = geom.height / 2;
const radius = Math.min(x, y);
// Draw the circle o/
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fill();
}
});
#myElement {
--circle-color: red;
background-image: paint(circle);
}
https://drafts.css-houdini.org/css-paint-api/#example-1
custom paint
where we’re
going
https://tc39.github.io/ecma262/
spec version numbers are bullshit
es6, es2015, es2016… who cares?
you should not.
coming soon
async function getUserImages() {
const response = await fetch("http://example.com/users");
const users = await response.json();
return Promise.all(users.map(async (u) => {
return {
name: u.name,
image: (await fetch(u.imageUrl)).body
};
}));
}
async/await
for (let i = 0; i < maxIterations; ++i) {
const zRe24 = SIMD.Float32x4.mul(zRe4, zRe4);
const zIm24 = SIMD.Float32x4.mul(zIm4, zIm4);
const mi4 = SIMD.Float32x4.lessThanOrEqual(SIMD.Float32x4.add(zRe24,
if (mi4.signMask === 0x00) {
break;
}
const newRe4 = SIMD.Float32x4.sub(zRe24, zIm24);
const newIm4 = SIMD.Float32x4.mul(SIMD.Float32x4.mul(two4, zRe4), zIm
zRe4 = SIMD.Float32x4.add(cRe4, newRe4);
zIm4 = SIMD.Float32x4.add(cIm4, newIm4);
count4 = SIMD.Int32x4.add(count4, SIMD.Int32x4.and(mi4, on4));
}
SIMD.js
https://github.com/PeterJensen/mandelbrot/blob/master/js/mandelbrot.js
watch out for
// 64 bit integers
const fifteen = 5UL + 10UL;
// Memory-efficient structurally-equal "structs"
const Point = new ValueType({ x: float64, y: float64 });
const point = new Point({ x: 1.5, y: 2.4 });
assert(point === new Point({ x: 1.5, y: 2.4 }));
// Custom literals and operator overloads
const romaineLettuce = 0x3B5323FFrgba;
const length = 50percent + 10em + 5px;
el.css.width += 2rem;
value types
https://github.com/nikomatsakis/typed-objects-explainer/
class BusinessLogic {
@performance
doImportantComputation() {
// ...
}
}
(new BusinessLogic()).doImportantComputation();
// => doImportantComputation: 102ms
decorators
https://www.npmjs.com/package/decorator-performance
startSpinner();
const p = fetch(url)
.then(r => r.json())
.then(data => fetch(data.otherUrl))
.then(res => res.text())
.then(text => updateUI(text))
.catch(err => showUIError(err))
.finally(stopSpinner);
cancelButton.onclick = () => p.cancel();
cancelable promises
async function* directoryEntries(path) {
const dir = await opendir(path);
try {
let entry;
async for (const entry of readdir(dir)) {
yield path.resolve(entry);
}
} finally {
await closedir(dir);
}
}
async iterators
<script type="module">
import a from "./a.js";
import { b } from "../b.js";
import $ from "https://cdn.example.com/jquery.js";
</script>
module loading
one last thing…
web assembly!?!?
https://github.com/WebAssembly/design
The State of JavaScript (2015)

The State of JavaScript (2015)

  • 1.
    the state ofjavascript
  • 2.
    hi, i’m domenic GoogleChrome TC39 WHATWG HTML and Streams Standards Promises jsdom
  • 3.
    let’s talk about >where we’ve come from > javascript today > new platform innovations > where we’re going
  • 4.
  • 5.
    history 1995: form validation,image rollovers 1997: ECMA-262 edition 1, Internet Explorer 4, DHTML 1999: ES3 (function expressions, try/catch/finally, regexps, …) 2005: Ajax 2006: jQuery 2008: V8 (the speed race begins), JS: The Good Parts 2009: ES5, Node.js, PhoneGap, JSConf, ServerJS/CommonJS 2010: Backbone.js, RequireJS 2012: Windows 8, Nodecopter 2013: Nodebots, next-gen MVC, Extensible Web, asm.js 2015: io.js, ES2015, MS Edge, Node.js 4, web assembly
  • 6.
  • 7.
  • 8.
    es2015: syntax Class sugar:class, extends, super Arrow functions: arr.map(x => x * x) Destructuring: var { x, y } = getPoint() Rest/spread: var [first, …rest] = els; Math.max(...myArray) Parameter defaults: function parseInt(x, base = 10) { } Block scoping: { let x = 5; const y = 10; }
  • 9.
    es2015: data structures Map:object-to-object O(1) access time Set: O(1) lookup and storage WeakMap/WeakSet: private state and branding Iteration protocol: for-of across anything, even user iterables
  • 10.
    es2015: game-changers Generators: lazysequences and async/await-like syntax Promises: standardized async; play well with generators Proxies: virtual objects, no more .get(…)/.set(…, …)! Template strings: jsx`<a href="${url}">${text}</a>` Subclassable built-ins: class Elements extends Array { … }
  • 11.
  • 12.
    new code styles { "no-var":2, "object-shorthand": 2, "prefer-arrow-callback": 2, "prefer-const": 2, "prefer-template": 2 } https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/document-type.js "use strict"; const NODE_TYPE = require("../living/node-type"); const privates = Symbol("DocumentType internal sl module.exports = core => { core.DocumentType = class DocumentType extends constructor(secret, ownerDocument, name, publ if (secret !== privates) { throw new TypeError("Invalid constructor" } super(ownerDocument); this[privates] = { name, publicId, systemId } get name() { return this[privates].name; } ⋮
  • 13.
    promises everywhere generators forasync https://github.com/domenic/wattsi-server/blob/master/lib/app.js const outDirectory = randomDirectoryName(); yield mkdirp(outDirectory); try { try { const result = yield execFile(config.watt const outputFile = path.join(outDirectory yield fs.writeFile(outputFile, `${result. } catch (e) { if (e.stdout) e.message = `${e.stdout}${e this.throw(e, 400); } const zipFilePath = `${outDirectory}.zip`; yield execFile("7za", ["a", "-tzip", "-r", this.type = "application/zip"; this.body = fs.createReadStream(zipFilePath } finally { yield rimraf(outDirectory); }
  • 14.
    transpile for browsers andnonstandard features https://github.com/unicorn-standard/react-callback-register @callbackRegister class MyComponent extends React.Component static propTypes = { onClick: React.PropTypes.func } @callbackRegister.on('click') descriptiveName() { // Do your stuff } render() { return <div {...this.callbacks} /> } }
  • 15.
  • 16.
  • 17.
    class CustomImage extendsHTMLElement { constructor(src) { super(); if (src !== undefined) this.src = src; } get src() { return (new URL(this.getAttribute("src"), this.baseURI)).href; } set src(value) { this.setAttribute("src", value); } [Element.attributeChanged](name) { if (name === "src") updateShadowDOM(this, this.src); } } document.registerElement("custom-image", CustomImage); https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Constructor-Dmitry.md custom elements
  • 18.
    https://www.npmjs.com/search?q=service+worker toolbox.precache(['/index.html', '/site.css', '/images/logo.png']); toolbox.cache('/data/2014/posts.json'); toolbox.uncache('/data/2013/posts.json'); toolbox.router.get('/:foo/index.html',(req, values) => { return new Response( `Handled a request for ${req.url} with :foo as ${values.foo}`); }); toolbox.router.get('/myapp/a', toolbox.networkFirst); toolbox.router.get('/myapp/b', toolbox.networkOnly); toolbox.router.get('/myapp/c', toolbox.cacheFirst); toolbox.router.get('/myapp/:page', toolbox.cacheOnly); toolbox.router.get('/(.*)', toolbox.fastest); https://github.com/GoogleChrome/sw-toolbox service workers
  • 19.
    registerPaint('circle', class { staticget inputProperties() { return ['--circle-color']; } paint(ctx, geom, properties) { // Change the fill color. ctx.fillStyle = properties.get('--circle-color'); // Determine the center point and radius. const x = geom.width / 2; const y = geom.height / 2; const radius = Math.min(x, y); // Draw the circle o/ ctx.beginPath(); ctx.arc(x, y, radius, 0, 2 * Math.PI, false); ctx.fill(); } }); #myElement { --circle-color: red; background-image: paint(circle); } https://drafts.css-houdini.org/css-paint-api/#example-1 custom paint
  • 20.
  • 21.
  • 22.
    spec version numbersare bullshit es6, es2015, es2016… who cares? you should not.
  • 23.
  • 24.
    async function getUserImages(){ const response = await fetch("http://example.com/users"); const users = await response.json(); return Promise.all(users.map(async (u) => { return { name: u.name, image: (await fetch(u.imageUrl)).body }; })); } async/await
  • 25.
    for (let i= 0; i < maxIterations; ++i) { const zRe24 = SIMD.Float32x4.mul(zRe4, zRe4); const zIm24 = SIMD.Float32x4.mul(zIm4, zIm4); const mi4 = SIMD.Float32x4.lessThanOrEqual(SIMD.Float32x4.add(zRe24, if (mi4.signMask === 0x00) { break; } const newRe4 = SIMD.Float32x4.sub(zRe24, zIm24); const newIm4 = SIMD.Float32x4.mul(SIMD.Float32x4.mul(two4, zRe4), zIm zRe4 = SIMD.Float32x4.add(cRe4, newRe4); zIm4 = SIMD.Float32x4.add(cIm4, newIm4); count4 = SIMD.Int32x4.add(count4, SIMD.Int32x4.and(mi4, on4)); } SIMD.js https://github.com/PeterJensen/mandelbrot/blob/master/js/mandelbrot.js
  • 26.
  • 27.
    // 64 bitintegers const fifteen = 5UL + 10UL; // Memory-efficient structurally-equal "structs" const Point = new ValueType({ x: float64, y: float64 }); const point = new Point({ x: 1.5, y: 2.4 }); assert(point === new Point({ x: 1.5, y: 2.4 })); // Custom literals and operator overloads const romaineLettuce = 0x3B5323FFrgba; const length = 50percent + 10em + 5px; el.css.width += 2rem; value types https://github.com/nikomatsakis/typed-objects-explainer/
  • 28.
    class BusinessLogic { @performance doImportantComputation(){ // ... } } (new BusinessLogic()).doImportantComputation(); // => doImportantComputation: 102ms decorators https://www.npmjs.com/package/decorator-performance
  • 29.
    startSpinner(); const p =fetch(url) .then(r => r.json()) .then(data => fetch(data.otherUrl)) .then(res => res.text()) .then(text => updateUI(text)) .catch(err => showUIError(err)) .finally(stopSpinner); cancelButton.onclick = () => p.cancel(); cancelable promises
  • 30.
    async function* directoryEntries(path){ const dir = await opendir(path); try { let entry; async for (const entry of readdir(dir)) { yield path.resolve(entry); } } finally { await closedir(dir); } } async iterators
  • 31.
    <script type="module"> import afrom "./a.js"; import { b } from "../b.js"; import $ from "https://cdn.example.com/jquery.js"; </script> module loading
  • 32.
  • 33.

Editor's Notes

  • #4 Some of what we’ll talk about will be review, and some of it will be pretty crazy stuff that you might not follow just from reading a single slide. I’ll be going over a lot, and not spending too much time on each thing. I promise I’ll put everything online after my talk, and you can investigate them in detail. I just want to give a broad overview, from the basics to the most far-out new stuff, so that you know what’s out there and can learn more on your own. Let’s start slow…
  • #8 The biggest news in JavaScript is that the language has undergone a major revision, with new features being introduced all over the place. I’ll go through these briefly, so you can get my take on what the most important point is about each feature.
  • #12 So that’s what’s actually in this newest version of the JavaScript language. How does it actually play out when we see it in real projects? It’s still somewhat early, but especially due to Node.js, there have been some trends starting. Here is what I’ve noticed:
  • #13 This is some code from the jsdom project I work on, which works strictly on the latest version of Node. const everywhere, or sometimes let; no var Use standard class syntax Use arrow functions whenever possible; here my module.exports Shorthand object literals, of course Using a symbol to store the private state ESLint rules to enforce some of these things, including one I didn’t talk about, using template strings instead of concatenation.
  • #14 The other big trend, of course, is to use promises for all asynchronous code, instead of for example Node.js-style callbacks. You can even go further, and use generators and the “yield” keyword as a sort of “await” substitute. So here I am “awaiting” mkdirp, and execFile, and fs.writeFile, and more. This code sample is from a small server I wrote which allows you to upload some files and get back a zip file. It uses the koa server framework. It does a lot of I/O, and you can see how the code flows entirely differently with ES15 features than with Node style callbacks. The promise return values are what make this possible. You can check out the full code sample to see how this all works, but the takeaway is that by having your module return promises, and by using tools like koa you can opt in to this much nicer style of code writing.
  • #15 The final trend, which goes beyond ES15 in many ways, is the extensive use of Babel as a transpiler. It is useful for old browser environments, where if you need to support Internet Explorer you’ll need to transpile down. And it is useful for code that uses nonstandard features. Here I have shown it used for decorators, class properties, and JSX, which are big features. Sometimes people say they will be in ES2016, but that is definitely not true; they are nonstandard. Babel is good for nonstandard stuff, but if you are just writing standard JavaScript, you might not need it. Node.js has almost everything Babel does, with a few notable exceptions like destructuring and parameter defaults. Neither Babel nor Node.js has proxies. Babel can transpile module syntax, but that is not a good idea, since nobody knows how modules work yet. It is mainly for when you need to support old Internet Explorer versions. (Edge has support for pretty much everything Chrome and Firefox support.)
  • #16 OK, so enough about the language for now. I know this is a Node conference, but I want to talk a bit about the cool ways that JavaScript is being used on the front-end, because in many ways that is where all the innovation is. The Node.js community and core team is focused a lot on stability and on keeping core small and such, and so there is not a lot of room for innovation in that regard. They are just playing catch up on es15 and such, and working on LTS releases and so on. So the browser is where the cool stuff is happening today, that I want to show off to you.
  • #17 The foundational reason the browser has so much exciting stuff going on connected to JavaScript is this document, called the “extensible web manifesto.” It’s essentially saying that the browser has too much C++ magic in it, and instead we should explain all the magic so that JavaScript code can do very low-level stuff at every point in the browser stack. We’ll see a bunch of examples of that coming up.
  • #18 A simple example of the kind of magic that we’re trying to let JavaScript explain is the HTML parser, and how it goes from the tags you write in your HTML files, to elements in the DOM tree. The custom elements API is what makes this possible. This API has been evangelized by the Chrome and Polymer teams for many years, but it’s been effectively a Chrome-only API for a long time. Unfortunately that’s still true right now, but we’re working very hard to come to an agreement on a design that everyone likes. And it’s better than the Chrome version, even.
  • #19 Service worker is another heavily-evangelized part of the extensible web movement. Not everyone realizes how powerful it is though. It is essentially a JavaScript programmable proxy server embedded between the web page and the internet. As such, you can apply a lot of the usual server-side programming techniques that we are used to from Node.js inside service workers. The library I am showing here, service worker toolbox, makes that very clear, with its Express-like router syntax. You could also imagine a koa-esque framework, which would be pretty cool, so you could write code like I showed earlier. Someone should build that!
  • #20 The final example I want to show where JavaScript is really getting involved in parts of the browser where it has never before had access is this custom paint API. This is actually one of a set of APIs, called “Houdini,” which seeks to do the same for all parts of CSS. But custom paint is probably the most far along, and also the easiest to explain. The idea here is that…
  • #21 OK cool. So I hope you guys enjoyed hearing a bit about the cool ways that JavaScript is being used on the front-end, even at a Node.js conference. As I said, maybe Node.js can take some inspiration from the way the web is exposing more and more of itself to JavaScript, and refusing to slow down its API evolution. Anyway, let’s go back to talking about the language. What does the future hold for JavaScript?
  • #22 Check out this URL. There are two magical things about it. First, the spec is now on GitHub! Second, the spec is now a living standard!
  • #23 The real takeaway from that URL, and indeed from modern standards development in general, is that spec version numbers are bullshit. You should never care about what version a spec is on, or what version a feature is introduced. Who cares? Like we talked about when talking about Babel, and when not to use it, what matters is what’s implemented in your target environment, or in the intersection of all your target environments. Features might change if they’re not implemented in many places, but they’ll be reasonably stable once they see wide implementation.
  • #24 So with that in mind, I’m not going to tell you about all the wonderful features coming in es16 or es17 or whatever. Instead, I’m going to tell you about some cool features that are coming relatively soon, because implementation has already started, and then some other features you might want to keep an eye out for, but nobody’s started implementing yet.
  • #25 Already implemented in Edge behind a flag, and Firefox nightly. This is happening! Lots of conference talks about this.
  • #26 Implemented in Firefox nightly, in development in Chrome and Edge. This is kind of an esoteric feature, but it’s about unlocking the possibilities of your hardware. Note how ugly it is, with the method names instead of nice operators.
  • #27 And that’s it, basically. There are a couple library functions in the works as well, but otherwise, all the rest of the future stuff I’m talking about is still pretty far out there. Maybe it’s in a transpiler, or maybe it’s not, but it’s unstable and may or may not make it in. That said, here are the things I think are particularly interesting to watch…
  • #28 One big area of interest is expanding the type system. Not in a static typing SoundScript sense or anything, but in terms of giving us new dynamic types that serve the language better. There are a lot of interesting ideas here, but they all kind of float around this idea of value objects.. ……
  • #29 Decorators are a feature that are getting a lot of thought from their champions in TC39. Essentially, they let you run a function on top of some syntactic element, and modify it (at runtime) to behave differently, doing code generation for example. In this example, the @performance decorator is wrapping the doImportantComputation() function to output how long it took. It’s actually replacing the original method that you declared with a new one that wraps it.
  • #30 An area that I’m personally responsible for working on is making promises cancelable. Here is some idea of what it might look like. The thing I am trying to show here is that we have a long promise chain, with lots of thens and a catch and a finally (which is new), and then we cancel the very last promise in the chain. This should be able to cancel any of the intermediate steps, depending on where we are.
  • #31 Another thing in the asynchronous realm is the idea of async iterators and generators. This is an example of an async function*, and an async for loop, which iterates over a directory, asynchronously yielding the directory entries. You can use await in here, to do async stuff. You can use yield in here, to generate a sequence. And you can use async for, to do async iteration. I think this is a really beautiful primitive for “asynchronous plurals,” and a very important one. Others on the committee are interested in something called “observables,” but those are a lot more awkward than async iterators in a variety of ways. I have another talk about that.
  • #32 And lastly…. The module loader. So yeah, modules are technically in es15. But nobody can use them, at least in a standard way, because there is no spec for loading them, just for their syntax. There are nonstandard ways of doing this. E.g. Babel or Traceur or TypeScript have their own module loader algorithms, which are not based on a spec and do not map to what will be implemented in engines, but are just made up, or just reuse the Node algorithm, or similar. Well… eventually maybe we’ll get a spec on this stuff. I think pretty much everyone involved knows a few things…
  • #33 And that’s pretty much it for the state of JavaScript… except… one last thing we should talk about…
  • #34 What is this web assembly business? Well… we don’t really know yet! The basic idea is to have some kind of second language in the browser VM, alongside JavaScript. It would be a compile target for languages like C++. Or maybe also languages like Java? But there is a lot of speculative talk. In some senses it’s basically just asm.js, but easier to compress and parse? But then, people are talking about adding threads and shared memory to it, maybe? There is a lot of effort going in to making sure it shares the same VM as the JavaScript VM, but how does that work exactly? Can wasm call web APIs? What if they return complex JavaScript objects, like a promise or a Map? I have a lot more questions than answers, but there’s serious implementer resources going toward this second language in our VMs, and it’s worth keeping an eye out. We’ll have to see.
  • #35 In the end, though, I’m not worried. JavaScript is here to stay, and it’s doing pretty well. Thank you.