From cc5213b09ef248e9f9db1892ec69930863b3055d Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 3 Jul 2019 17:19:00 +0300 Subject: [PATCH] updates --- .../10-date/1-new-date/solution.md | 2 +- .../10-date/4-get-date-ago/task.md | 2 +- 1-js/05-data-types/10-date/article.md | 22 +- .../03-static-properties-methods/article.md | 4 +- .../3-events-change-input/article.md | 2 +- .../4-forms-submit/article.md | 2 +- .../01-popup-windows/article.md | 135 +++++++++--- .../03-cross-window-communication/article.md | 121 +++++----- 4-binary/04-file/article.md | 30 ++- .../01-fetch-users/_js.view/solution.js | 0 .../01-fetch-users/_js.view/source.js | 0 .../01-fetch-users/_js.view/test.js | 0 .../01-fetch-users/solution.md | 0 .../01-fetch-users/task.md | 0 .../{01-fetch-basics => 01-fetch}/article.md | 130 ++++------- .../logo-fetch.svg | 0 .../post.view/server.js | 23 -- 5-network/02-formdata/article.md | 183 ++++++++++++++++ 5-network/02-formdata/post.view/server.js | 78 +++++++ .../article.md | 28 ++- .../logo-fetch.svg | 0 .../progress.view/index.html | 0 .../progress.view/long.txt | 0 .../article.md | 0 .../demo.view/server.js | 0 .../1-do-we-need-origin/solution.md | 0 .../1-do-we-need-origin/task.md | 0 .../article.md | 0 .../cors-gmail-messages.png | Bin .../cors-gmail-messages@2x.png | Bin .../demo.view/index.html | 0 .../demo.view/server.js | 0 .../xhr-another-domain.png | Bin .../xhr-another-domain@2x.png | Bin .../xhr-preflight.png | Bin .../xhr-preflight@2x.png | Bin .../{05-fetch-api => 06-fetch-api}/article.md | 2 +- .../logo-fetch.svg | 0 .../post.view/index.html | 0 .../post.view/server.js | 0 5-network/06-url/article.md | 105 --------- 5-network/06-url/url-object.png | Bin 15971 -> 0 bytes 5-network/06-url/url-object@2x.png | Bin 35844 -> 0 bytes 5-network/07-url/article.md | 206 ++++++++++++++++++ 5-network/07-url/url-object.png | Bin 0 -> 16027 bytes 5-network/07-url/url-object@2x.png | Bin 0 -> 36536 bytes .../article.md | 47 ++-- .../example.view/index.html | 0 .../example.view/server.js | 0 .../hello.txt | 0 .../phones-async.view/index.html | 0 .../phones-async.view/phones.json | 0 .../phones-async.view/server.js | 0 .../phones.json | 0 .../phones.view/index.html | 0 .../phones.view/phones.json | 0 .../phones.view/server.js | 0 .../post.view/index.html | 0 .../post.view/server.js | 0 5-network/09-resume-upload/article.md | 75 +++++++ .../upload-resume.view/index.html | 50 +++++ .../upload-resume.view/server.js | 122 +++++++++++ .../upload-resume.view/uploader.js | 75 +++++++ 5-network/10-long-polling/article.md | 95 ++++++++ 5-network/10-long-polling/long-polling.png | Bin 0 -> 18962 bytes 5-network/10-long-polling/long-polling@2x.png | Bin 0 -> 48832 bytes .../10-long-polling/longpoll.view/browser.js | 54 +++++ .../10-long-polling/longpoll.view/index.html | 18 ++ .../10-long-polling/longpoll.view/server.js | 87 ++++++++ .../{08-websocket => 11-websocket}/article.md | 0 .../chat.view/index.html | 0 .../chat.view/server.js | 0 .../demo.view/server.js | 0 .../websocket-handshake.png | Bin .../websocket-handshake@2x.png | Bin .../article.md | 0 .../eventsource.view/index.html | 0 .../eventsource.view/server.js | 0 figures.sketch | Bin 5830922 -> 5461808 bytes 79 files changed, 1341 insertions(+), 357 deletions(-) rename 5-network/{01-fetch-basics => 01-fetch}/01-fetch-users/_js.view/solution.js (100%) rename 5-network/{01-fetch-basics => 01-fetch}/01-fetch-users/_js.view/source.js (100%) rename 5-network/{01-fetch-basics => 01-fetch}/01-fetch-users/_js.view/test.js (100%) rename 5-network/{01-fetch-basics => 01-fetch}/01-fetch-users/solution.md (100%) rename 5-network/{01-fetch-basics => 01-fetch}/01-fetch-users/task.md (100%) rename 5-network/{01-fetch-basics => 01-fetch}/article.md (70%) rename 5-network/{01-fetch-basics => 01-fetch}/logo-fetch.svg (100%) rename 5-network/{01-fetch-basics => 01-fetch}/post.view/server.js (55%) create mode 100644 5-network/02-formdata/article.md create mode 100644 5-network/02-formdata/post.view/server.js rename 5-network/{02-fetch-progress => 03-fetch-progress}/article.md (73%) rename 5-network/{02-fetch-progress => 03-fetch-progress}/logo-fetch.svg (100%) rename 5-network/{02-fetch-progress => 03-fetch-progress}/progress.view/index.html (100%) rename 5-network/{02-fetch-progress => 03-fetch-progress}/progress.view/long.txt (100%) rename 5-network/{03-fetch-abort => 04-fetch-abort}/article.md (100%) rename 5-network/{03-fetch-abort => 04-fetch-abort}/demo.view/server.js (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/1-do-we-need-origin/solution.md (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/1-do-we-need-origin/task.md (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/article.md (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/cors-gmail-messages.png (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/cors-gmail-messages@2x.png (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/demo.view/index.html (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/demo.view/server.js (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/xhr-another-domain.png (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/xhr-another-domain@2x.png (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/xhr-preflight.png (100%) rename 5-network/{04-fetch-crossorigin => 05-fetch-crossorigin}/xhr-preflight@2x.png (100%) rename 5-network/{05-fetch-api => 06-fetch-api}/article.md (99%) rename 5-network/{05-fetch-api => 06-fetch-api}/logo-fetch.svg (100%) rename 5-network/{05-fetch-api => 06-fetch-api}/post.view/index.html (100%) rename 5-network/{05-fetch-api => 06-fetch-api}/post.view/server.js (100%) delete mode 100644 5-network/06-url/article.md delete mode 100644 5-network/06-url/url-object.png delete mode 100644 5-network/06-url/url-object@2x.png create mode 100644 5-network/07-url/article.md create mode 100644 5-network/07-url/url-object.png create mode 100644 5-network/07-url/url-object@2x.png rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/article.md (94%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/example.view/index.html (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/example.view/server.js (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/hello.txt (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones-async.view/index.html (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones-async.view/phones.json (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones-async.view/server.js (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones.json (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones.view/index.html (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones.view/phones.json (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/phones.view/server.js (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/post.view/index.html (100%) rename 5-network/{07-xmlhttprequest => 08-xmlhttprequest}/post.view/server.js (100%) create mode 100644 5-network/09-resume-upload/article.md create mode 100644 5-network/09-resume-upload/upload-resume.view/index.html create mode 100644 5-network/09-resume-upload/upload-resume.view/server.js create mode 100644 5-network/09-resume-upload/upload-resume.view/uploader.js create mode 100644 5-network/10-long-polling/article.md create mode 100644 5-network/10-long-polling/long-polling.png create mode 100644 5-network/10-long-polling/long-polling@2x.png create mode 100644 5-network/10-long-polling/longpoll.view/browser.js create mode 100644 5-network/10-long-polling/longpoll.view/index.html create mode 100644 5-network/10-long-polling/longpoll.view/server.js rename 5-network/{08-websocket => 11-websocket}/article.md (100%) rename 5-network/{08-websocket => 11-websocket}/chat.view/index.html (100%) rename 5-network/{08-websocket => 11-websocket}/chat.view/server.js (100%) rename 5-network/{08-websocket => 11-websocket}/demo.view/server.js (100%) rename 5-network/{08-websocket => 11-websocket}/websocket-handshake.png (100%) rename 5-network/{08-websocket => 11-websocket}/websocket-handshake@2x.png (100%) rename 5-network/{09-server-sent-events => 12-server-sent-events}/article.md (100%) rename 5-network/{09-server-sent-events => 12-server-sent-events}/eventsource.view/index.html (100%) rename 5-network/{09-server-sent-events => 12-server-sent-events}/eventsource.view/server.js (100%) diff --git a/1-js/05-data-types/10-date/1-new-date/solution.md b/1-js/05-data-types/10-date/1-new-date/solution.md index eb271a91..9bb1d749 100644 --- a/1-js/05-data-types/10-date/1-new-date/solution.md +++ b/1-js/05-data-types/10-date/1-new-date/solution.md @@ -1,4 +1,4 @@ -The `new Date` constructor uses the local time zone by default. So the only important thing to remember is that months start from zero. +The `new Date` constructor uses the local time zone. So the only important thing to remember is that months start from zero. So February has number 1. diff --git a/1-js/05-data-types/10-date/4-get-date-ago/task.md b/1-js/05-data-types/10-date/4-get-date-ago/task.md index 40dcd926..058d39c7 100644 --- a/1-js/05-data-types/10-date/4-get-date-ago/task.md +++ b/1-js/05-data-types/10-date/4-get-date-ago/task.md @@ -8,7 +8,7 @@ Create a function `getDateAgo(date, days)` to return the day of month `days` ago For instance, if today is 20th, then `getDateAgo(new Date(), 1)` should be 19th and `getDateAgo(new Date(), 2)` should be 18th. -Should also work over months/years reliably: +Should work reliably for `days=365` or more: ```js let date = new Date(2015, 0, 2); diff --git a/1-js/05-data-types/10-date/article.md b/1-js/05-data-types/10-date/article.md index f8628d88..512bc712 100644 --- a/1-js/05-data-types/10-date/article.md +++ b/1-js/05-data-types/10-date/article.md @@ -40,7 +40,7 @@ To create a new `Date` object call `new Date()` with one of the following argume ```js run let date = new Date("2017-01-26"); alert(date); - // The time portion of the date is assumed to be midnight GMT and + // The time is not set, so it's assumed to be midnight GMT and // is adjusted according to the timezone the code is run in // So the result could be // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) @@ -51,8 +51,6 @@ To create a new `Date` object call `new Date()` with one of the following argume `new Date(year, month, date, hours, minutes, seconds, ms)` : Create the date with the given components in the local time zone. Only the first two arguments are obligatory. - Note: - - The `year` must have 4 digits: `2013` is okay, `98` is not. - The `month` count starts with `0` (Jan), up to `11` (Dec). - The `date` parameter is actually the day of month, if absent then `1` is assumed. @@ -74,7 +72,7 @@ To create a new `Date` object call `new Date()` with one of the following argume ## Access date components -There are many methods to access the year, month and so on from the `Date` object. But they can be easily remembered when categorized. +There are methods to access the year, month and so on from the `Date` object: [getFullYear()](mdn:js/Date/getFullYear) : Get the year (4 digits) @@ -217,21 +215,21 @@ The important side effect: dates can be subtracted, the result is their differen That can be used for time measurements: ```js run -let start = new Date(); // start counting +let start = new Date(); // start measuring time // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } -let end = new Date(); // done +let end = new Date(); // end measuring time alert( `The loop took ${end - start} ms` ); ``` ## Date.now() -If we only want to measure the difference, we don't need the `Date` object. +If we only want to measure time, we don't need the `Date` object. There's a special method `Date.now()` that returns the current timestamp. @@ -264,6 +262,8 @@ If we want a reliable benchmark of CPU-hungry function, we should be careful. For instance, let's measure two functions that calculate the difference between two dates: which one is faster? +Such performance measurements are often called "benchmarks". + ```js // we have date1 and date2, which function faster returns their difference in ms? function diffSubtract(date1, date2) { @@ -280,7 +280,7 @@ These two do exactly the same thing, but one of them uses an explicit `date.getT So, which one is faster? -The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it around 100000 times. +The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it at least 100000 times. Let's measure: @@ -310,7 +310,7 @@ Wow! Using `getTime()` is so much faster! That's because there's no type convers Okay, we have something. But that's not a good benchmark yet. -Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` the work has finished. +Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` that work has finished. A pretty real scenario for a modern multi-process OS. @@ -368,7 +368,7 @@ for (let i = 0; i < 10; i++) { ``` ```warn header="Be careful doing microbenchmarking" -Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. +Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. The great pack of articles about V8 can be found at . ``` @@ -415,7 +415,7 @@ alert(date); Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds. -Also, sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): +Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): ```js run alert(`Loading started ${performance.now()}ms ago`); diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index b940e04c..77bf092f 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -17,10 +17,10 @@ class User { User.staticMethod(); // true ``` -That actually does the same as assigning it as a function property: +That actually does the same as assigning it as a property: ```js -function User() { } +class User() { } User.staticMethod = function() { alert(this === User); diff --git a/2-ui/4-forms-controls/3-events-change-input/article.md b/2-ui/4-forms-controls/3-events-change-input/article.md index e0d36c0b..439447b0 100644 --- a/2-ui/4-forms-controls/3-events-change-input/article.md +++ b/2-ui/4-forms-controls/3-events-change-input/article.md @@ -78,7 +78,7 @@ Please note, that it's possible to copy/paste not just text, but everything. For There's a list of methods [in the specification](https://www.w3.org/TR/clipboard-apis/#dfn-datatransfer) that can work with different data types including files, read/write to the clipboard. -But please note that clipboard is a "global" OS-level thing. Most browsers allow read/write access to the clipboard only in the scope of certain user actions for the safety. +But please note that clipboard is a "global" OS-level thing. Most browsers allow read/write access to the clipboard only in the scope of certain user actions for the safety, e.g. in `onclick` event handlers. Also it's forbidden to generate "custom" clipboard events with `dispatchEvent` in all browsers except Firefox. diff --git a/2-ui/4-forms-controls/4-forms-submit/article.md b/2-ui/4-forms-controls/4-forms-submit/article.md index 69d5a3dc..c00c559c 100644 --- a/2-ui/4-forms-controls/4-forms-submit/article.md +++ b/2-ui/4-forms-controls/4-forms-submit/article.md @@ -1,4 +1,4 @@ -# Form submission: event and method submit +# Forms: event and method submit The `submit` event triggers when the form is submitted, it is usually used to validate the form before sending it to the server or to abort the submission and process it in JavaScript. diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 52d6768a..e90d6a7b 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -7,12 +7,20 @@ Basically, you just run: window.open('https://javascript.info/') ``` -... And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows. +...And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows. + +Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with [fetch](info:fetch) and show it in a dynamically generated `
`. So, popups is not something we use everyday. + +Also, popups are tricky on mobile devices. + +Still, there are situations when a popup works good, e.g. for OAuth authorization (login with Google/Facebook/...), because: + +1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe. +2. It's very easy to open a popup, little to no overhead. +3. A popup can navigate (change URL) and send messages to the opener window. ## Popup blocking -Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: JavaScript is able to send requests for server, so popups are rarely used. But sometimes they are still handy. - In the past, evil sites abused popups a lot. A bad page could open tons of popup windows with ads. So now most browsers try to block popups and protect the user. **Most browsers block popups if they are called outside of user-triggered event handlers like `onclick`.** @@ -50,14 +58,6 @@ setTimeout(() => window.open('http://google.com'), 1000); The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. -## Modern usage - -As of now, we have many methods to load and show data on-page with JavaScript. But there are still situations when a popup works good, because: - -1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe. -2. It's very easy to open a popup, little to no overhead. -3. A popup may persist even if the user left the page. In also can navigate (change URL) in the opener window. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: @@ -118,16 +118,26 @@ Rules for omitted settings: - If there is no `left/top` in params, then the browser tries to open a new window near the last opened window. - If there is no `width/height`, then the new window will be the same size as the last opened. -## Accessing a popup +## Accessing popup from window The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more. -In the example below, the contents of the new window is modified after loading. +In this example, we generate popup content from JavaScript: + +```js +let newWin = window.open("about:blank", "hello", "width=200,height=200"); + +newWin.document.write("Hello, world!"); +``` + +And here we modify the contents after loading: ```js run let newWindow = open('/', 'example', 'width=300,height=300') newWindow.focus(); +alert(newWin.location.href); // (*) about:blank, loading hasn't started yet + newWindow.onload = function() { let html = `
Welcome!
`; *!* @@ -136,35 +146,95 @@ newWindow.onload = function() { }; ``` -Please note that external `document` content is only accessible for windows from the same origin (the same protocol://domain:port). +Please note: immediately after `window.open`, the new window isn't loaded yet. That's demonstrated by `alert` in line `(*)`. So we wait for `onload` to modify it. We could also use `DOMContentLoaded` handler for `newWin.document`. -For windows with URLs from another sites, we are able to change the location by assigning `newWindow.location=...`, but we can't read the location or access the content. That's for user safety, so that an evil page can't open a popup with `http://gmail.com` and read the data. We'll talk more about it later. +```warn header="Same origin policy" +Windows may only freely modify each other if they come from the same origin (the same protocol://domain:port). -## Accessing the opener window +Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail.com`, that's impossible for user safety reasons. For the details, see chapter . +``` -A popup may access the "opener" window as well. A JavaScript in it may use `window.opener` to access the window that opened it. It is `null` for all windows except popups. +## Accessing window from popup -So both the main window and the popup have a reference to each other. They may modify each other freely assuming that they come from the same origin. If that's not so, then there are still means to communicate, to be covered in the next chapter . +A popup may access the "opener" window as well using `window.opener` reference. It is `null` for all windows except popups. + +If you run the code below, it replaces the opener window content with "Test": + +```js run +let newWin = window.open("about:blank", "hello", "width=200,height=200"); + +newWin.document.write( + " ``` The code above shows errors for any operations except: -- Getting the reference to the inner window `iframe.contentWindow` -- Changing its `location`. +- Getting the reference to the inner window `iframe.contentWindow` - that's allowed. +- Writing to `location`. -```smart header="`iframe.onload` vs `iframe.contentWindow.onload`" -The `iframe.onload` event is essentially the same as `iframe.contentWindow.onload`. It triggers when the embedded window fully loads with all resources. - -...But `iframe.onload` is always available from outside the iframe, while accessing `iframe.contentWindow.onload` is only permitted from the window with the same origin. -``` - -And now an example with the same origin. We can do anything with the embedded window: +Contrary to that, if the ` ``` -### Please wait until the iframe loads +```smart header="`iframe.onload` vs `iframe.contentWindow.onload`" +The `iframe.onload` event (on the ` @@ -173,7 +181,7 @@ But for the same origin we can setup the event handler. We just need to set it o ``` -## window.frames +## Collection: window.frames An alternative way to get a window object for `