diff --git a/1-js/01-getting-started/2-code-editors/article.md b/1-js/01-getting-started/2-code-editors/article.md index d36561bc6..6532e54a3 100644 --- a/1-js/01-getting-started/2-code-editors/article.md +++ b/1-js/01-getting-started/2-code-editors/article.md @@ -12,14 +12,12 @@ An IDE loads the project (which can be many files), allows navigation between fi If you haven't selected an IDE yet, consider the following options: -- [WebStorm](http://www.jetbrains.com/webstorm/) for frontend development. The same company offers other editors for other languages (paid). -- [Netbeans](http://netbeans.org/) (free). +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +- [WebStorm](http://www.jetbrains.com/webstorm/) (cross-platform, paid). -All of these IDEs are cross-platform. +For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version [Visual Studio Community](https://www.visualstudio.com/vs/community/). -For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code." "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. A free version of it is called [Visual Studio Community](https://www.visualstudio.com/vs/community/). - -Many IDEs are paid but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. +Many IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. ## Lightweight editors @@ -33,21 +31,11 @@ In practice, lightweight editors may have a lot of plugins including directory-l The following options deserve your attention: -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free) also has many IDE-like features. - [Atom](https://atom.io/) (cross-platform, free). - [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, free). - [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. -## My favorites - -The personal preference of the author is to have both an IDE for projects and a lightweight editor for quick and easy file editing. - -I'm using: - -- As an IDE for JS -- [WebStorm](http://www.jetbrains.com/webstorm/) (I switch to one of the other JetBrains offerings when using other languages) -- As a lightweight editor -- [Sublime Text](http://www.sublimetext.com) or [Atom](https://atom.io/). - ## Let's not argue The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with. diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md index 8323d6643..b3dfe9e14 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/03-symbol/article.md @@ -16,7 +16,7 @@ A value of this type can be created using `Symbol()`: let id = Symbol(); ``` -We can also give symbol a description (also called a symbol name), mostly useful for debugging purposes: +Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes: ```js run // id is a symbol with the description "id" @@ -74,7 +74,7 @@ alert(id.description); // id Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite. -For instance, if we want to store an "identifier" for the object `user`, we can use a symbol as a key for it: +For instance, if we'd like to add an "identifier" to the object `user`, we can use a symbol as a key for it: ```js run let user = { name: "John" }; @@ -88,7 +88,7 @@ What's the benefit of using `Symbol("id")` over a string `"id"`? Let's make the example a bit deeper to see that. -Imagine that another script wants to have its own "id" property inside `user`, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other. +Imagine that another script wants to have its own identifier inside `user`, for its own purposes. That may be another JavaScript library, so thes scripts are completely unaware of each other. Then that script can create its own `Symbol("id")`, like this: @@ -99,9 +99,9 @@ let id = Symbol("id"); user[id] = "Their id value"; ``` -There will be no conflict, because symbols are always different, even if they have the same name. +There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name. -Now note that if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: +...But if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: ```js run let user = { name: "John" }; @@ -117,7 +117,7 @@ user.id = "Their id value" ### Symbols in a literal -If we want to use a symbol in an object literal, we need square brackets. +If we want to use a symbol in an object literal `{...}`, we need square brackets around it. Like this: @@ -155,7 +155,7 @@ for (let key in user) alert(key); // name, age (no symbols) alert( "Direct: " + user[id] ); ``` -That's a part of the general "hiding" concept. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. +`Object.keys(user)` also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties: @@ -190,13 +190,13 @@ alert( obj[0] ); // test (same property) ## Global symbols -As we've seen, usually all symbols are different, even if they have the same names. But sometimes we want same-named symbols to be same entities. +As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. To achieve that, there exists a *global symbol registry*. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol. -In order to create or read a symbol in the registry, use `Symbol.for(key)`. +In order to read (create if absent) a symbol from the registry, use `Symbol.for(key)`. That call checks the global registry, and if there's a symbol described as `key`, then returns it, otherwise creates a new symbol `Symbol(key)` and stores it in the registry by the given `key`. @@ -206,7 +206,7 @@ For instance: // read from the global registry let id = Symbol.for("id"); // if the symbol did not exist, it is created -// read it again +// read it again (maybe from another part of the code) let idAgain = Symbol.for("id"); // the same symbol @@ -266,14 +266,14 @@ Other symbols will also become familiar when we study the corresponding language `Symbol` is a primitive type for unique identifiers. -Symbols are created with `Symbol()` call with an optional description. +Symbols are created with `Symbol()` call with an optional description (name). -Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(key)` returns (creates if needed) a global symbol with `key` as the name. Multiple calls of `Symbol.for` return exactly the same symbol. +Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(key)` returns (creates if needed) a global symbol with `key` as the name. Multiple calls of `Symbol.for` with the same `key` return exactly the same symbol. Symbols have two main use cases: 1. "Hidden" object properties. - If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be occasionally listed. Also it won't be accessed directly, because another script does not have our symbol, so it will not occasionally intervene into its actions. + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be occasionally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from occasional use or overwrite. So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties. diff --git a/1-js/05-data-types/03-string/2-check-spam/task.md b/1-js/05-data-types/03-string/2-check-spam/task.md index 3e7220951..98b5dd8a0 100644 --- a/1-js/05-data-types/03-string/2-check-spam/task.md +++ b/1-js/05-data-types/03-string/2-check-spam/task.md @@ -4,7 +4,7 @@ importance: 5 # Check for spam -Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise false. +Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false`. The function must be case-insensitive: diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 3e9300793..16d14071f 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -16,7 +16,7 @@ The array in the process: ```js no-beautify Jazz, Blues -Jazz, Bues, Rock-n-Roll +Jazz, Blues, Rock-n-Roll Jazz, Classics, Rock-n-Roll Classics, Rock-n-Roll Rap, Reggae, Classics, Rock-n-Roll diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 2d6c225c9..c91a9e233 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -453,7 +453,7 @@ We can use an array as a deque with the following operations: - `push(...items)` adds `items` to the end. - `pop()` removes the element from the end and returns it. - `shift()` removes the element from the beginning and returns it. -- `unshift(...items)` adds items to the beginning. +- `unshift(...items)` adds `items` to the beginning. To loop over the elements of the array: - `for (let i=0; i { alert(`${key}: ${value}`); // cucumber: 500 etc }); @@ -172,7 +189,7 @@ A `Set` is a collection of values, where each value may occur only once. Its main methods are: -- `new Set(iterable)` -- creates the set, optionally from an array of values (any iterable will do). +- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. - `set.add(value)` -- adds a value, returns the set itself. - `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. - `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. @@ -222,9 +239,9 @@ set.forEach((value, valueAgain, set) => { }); ``` -Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a value, then *again a value*, and then the target object. Indeed, the same value appears in the arguments twice. +Note the funny thing. The callback function passed in `forEach` has 3 arguments: a value, then *again a value*, and then the target object. Indeed, the same value appears in the arguments twice. -That's for compatibility with `Map` where `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. The same methods `Map` has for iterators are also supported: diff --git a/1-js/05-data-types/08-keys-values-entries/article.md b/1-js/05-data-types/08-keys-values-entries/article.md index 66ca3ca92..3780b33e5 100644 --- a/1-js/05-data-types/08-keys-values-entries/article.md +++ b/1-js/05-data-types/08-keys-values-entries/article.md @@ -1,11 +1,11 @@ # Object.keys, values, entries -Let's step away from the individual data structures and talk about the iterations over them. +Let's step away from the individual data structures and talk about the iterations over them. In the previous chapter we saw methods `map.keys()`, `map.values()`, `map.entries()`. -These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. +These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. They are supported for: @@ -63,8 +63,93 @@ for (let value of Object.values(user)) { } ``` -## Object.keys/values/entries ignore symbolic properties - +```warn header="Object.keys/values/entries ignore symbolic properties" Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys. -Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns *all* keys. +Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys. +``` + +## Object.fromEntries to transform objects + +Sometimes we need to perform a transformation of an object to `Map` and back. + +We already have `new Map(Object.entries(obj))` to make a `Map` from `obj`. + +The syntax of `Object.fromEntries` does the reverse. Given an array of `[key, value]` pairs, it creates an object: + +```js run +let prices = Object.fromEntries([ + ['banana', 1], + ['orange', 2], + ['meat', 4] +]); + +// now prices = { banana: 1, orange: 2, meat: 4 } + +alert(prices.orange); // 2 +``` + +Let's see practical applications. + +For example, we'd like to create a new object with double prices from the existing one. + +For arrays, we have `.map` method that allows to transform an array, but nothing like that for objects. + +So we can use a loop: + +```js run +let prices = { + banana: 1, + orange: 2, + meat: 4, +}; + +let doublePrices = {}; +for(let [product, price] of Object.entries(prices)) { + doublePrices[product] = price * 2; +} + +alert(doublePrices.meat); // 8 +``` + +...Or we can represent the object as an `Array` using `Object.entries`, then perform the operations with `map` (and potentially other array methods), and then go back using `Object.fromEntries`. + +Let's do it for our object: + +```js run +let prices = { + banana: 1, + orange: 2, + meat: 4, +}; + +*!* +let doublePrices = Object.fromEntries( + // convert to array, map, and then fromEntries gives back the object + Object.entries(prices).map(([key, value]) => [key, value * 2]) +); +*/!* + +alert(doublePrices.meat); // 8 +``` + +It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. + +We also can use `fromEntries` to get an object from `Map`. + +E.g. we have a `Map` of prices, but we need to pass it to a 3rd-party code that expects an object. + +Here we go: + +```js run +let map = new Map(); +map.set('banana', 1); +map.set('orange', 2); +map.set('meat', 4); + +let obj = Object.fromEntries(map); + +// now obj = { banana: 1, orange: 2, meat: 4 } + +alert(obj.orange); // 2 +``` diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 237b9ef9e..3a281ef3f 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. -P.P.S. The standard describes a "tail call" optimization: if the recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution and we don't need to remember its execution context. In that case `sumTo(100000)` is countable. But if your JavaScript engine does not support it, there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index a9ba0baf5..4357ff208 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -37,7 +37,7 @@ The loop variant is also a little bit more complicated then the direct output. There is no way to get the last value in our `list`. We also can't "go back". -So what we can do is to first go through the items in the direct order and rememeber them in an array, and then output what we remembered in the reverse order: +So what we can do is to first go through the items in the direct order and remember them in an array, and then output what we remembered in the reverse order: ```js run let list = { diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 735c75bf3..214de18c2 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -85,7 +85,7 @@ So, the recursion reduces a function call to a simpler one, and then -- to even ````smart header="Recursion is usually shorter" A recursive solution is usually shorter than an iterative one. -Here we can rewrite the same using the ternary `?` operator instead of `if` to make `pow(x, n)` more terse and still very readable: +Here we can rewrite the same using the conditional operator `?` instead of `if` to make `pow(x, n)` more terse and still very readable: ```js run function pow(x, n) { @@ -100,11 +100,11 @@ The maximal recursion depth is limited by JavaScript engine. We can make sure ab That limits the application of recursion, but it still remains very wide. There are many tasks where recursive way of thinking gives simpler code, easier to maintain. -## The execution stack +## The execution context and stack Now let's examine how recursive calls work. For that we'll look under the hood of functions. -The information about a function run is stored in its *execution context*. +The information about the process of execution of a running function is stored in its *execution context*. The [execution context](https://tc39.github.io/ecma262/#sec-execution-contexts) is an internal data structure that contains details about the execution of a function: where the control flow is now, the current variables, the value of `this` (we don't use it here) and few other internal details. @@ -416,7 +416,7 @@ let arr = [obj1, obj2, obj3]; ...But there's a problem with arrays. The "delete element" and "insert element" operations are expensive. For instance, `arr.unshift(obj)` operation has to renumber all elements to make room for a new `obj`, and if the array is big, it takes time. Same with `arr.shift()`. -The only structural modifications that do not require mass-renumbering are those that operate with the end of array: `arr.push/pop`. So an array can be quite slow for big queues. +The only structural modifications that do not require mass-renumbering are those that operate with the end of array: `arr.push/pop`. So an array can be quite slow for big queues, when we have to work with the beginning. Alternatively, if we really need fast insertion/deletion, we can choose another data structure called a [linked list](https://en.wikipedia.org/wiki/Linked_list). @@ -506,14 +506,17 @@ Naturally, lists are not always better than arrays. Otherwise everyone would use The main drawback is that we can't easily access an element by its number. In an array that's easy: `arr[n]` is a direct reference. But in the list we need to start from the first item and go `next` `N` times to get the Nth element. -...But we don't always need such operations. For instance, when we need a queue or even a [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- the ordered structure that must allow very fast adding/removing elements from both ends. +...But we don't always need such operations. For instance, when we need a queue or even a [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- the ordered structure that must allow very fast adding/removing elements from both ends, but access to its middle is not needed. -Sometimes it's worth to add another variable named `tail` to track the last element of the list (and update it when adding/removing elements from the end). For large sets of elements the speed difference versus arrays is huge. +Lists can be enhanced: +- We can add property `prev` in addition to `next` to reference the previous element, to move back easily. +- We can also add a variable named `tail` referencing the last element of the list (and update it when adding/removing elements from the end). +- ...The data structure may vary according to our needs. ## Summary Terms: -- *Recursion* is a programming term that means a "self-calling" function. Such functions can be used to solve certain tasks in elegant ways. +- *Recursion* is a programming term that means calling a function from itself. Recursive functions can be used to solve tasks in elegant ways. When a function calls itself, that's called a *recursion step*. The *basis* of recursion is function arguments that make the task so simple that the function does not make further calls. diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 0a3666641..dfab568d2 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -35,7 +35,7 @@ alert(phrase); // Error, phrase is not defined For instance: -```js +```js run if (true) { var test = true; // use "var" instead of "let" } @@ -61,7 +61,7 @@ alert(i); // 10, "i" is visible after loop, it's a global variable If a code block is inside a function, then `var` becomes a function-level variable: -```js +```js run function sayHi() { if (true) { var phrase = "Hello"; @@ -71,7 +71,7 @@ function sayHi() { } sayHi(); -alert(phrase); // Error: phrase is not defined +alert(phrase); // Error: phrase is not defined (Check the Developer Console) ``` As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that. @@ -84,7 +84,7 @@ In other words, `var` variables are defined from the beginning of the function, So this code: -```js +```js run function sayHi() { phrase = "Hello"; @@ -94,11 +94,12 @@ function sayHi() { var phrase; */!* } +sayHi(); ``` ...Is technically the same as this (moved `var phrase` above): -```js +```js run function sayHi() { *!* var phrase; @@ -108,11 +109,12 @@ function sayHi() { alert(phrase); } +sayHi(); ``` ...Or even as this (remember, code blocks are ignored): -```js +```js run function sayHi() { phrase = "Hello"; // (*) @@ -124,6 +126,7 @@ function sayHi() { alert(phrase); } +sayHi(); ``` People also call such behavior "hoisting" (raising), because all `var` are "hoisted" (raised) to the top of the function. diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index da4adc2b6..742132aae 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -1,11 +1,13 @@ # Global object -The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the host environment. +The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the environment. -In a browser it is named "window", for Node.js it is "global", for other environments it may have another name. +In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name. -For instance, we can call `alert` as a method of `window`: +Recently, `globalThis` was added to the language, as a standartized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. + +All properties of the global object can be accessed directly: ```js run alert("Hello"); @@ -14,142 +16,72 @@ alert("Hello"); window.alert("Hello"); ``` -We can reference other built-in functions like `Array` as `window.Array` and create our own properties on it. - -## Browser: the "window" object - -For historical reasons, in-browser `window` object is a bit messed up. - -1. It provides the "browser window" functionality, besides playing the role of a global object. - - We can use `window` to access properties and methods, specific to the browser window: - - ```js run - alert(window.innerHeight); // shows the browser window height - - window.open('http://google.com'); // opens a new browser window - ``` - -2. Top-level `var` variables and function declarations automatically become properties of `window`. - - For instance: - ```js untrusted run no-strict refresh - var x = 5; - - alert(window.x); // 5 (var x becomes a property of window) - - window.x = 0; - - alert(x); // 0, variable modified - ``` - - Please note, that doesn't happen with more modern `let/const` declarations: - - ```js untrusted run no-strict refresh - let x = 5; - - alert(window.x); // undefined ("let" doesn't create a window property) - ``` - -3. Also, all scripts share the same global scope, so variables declared in one ` - - - ``` +In a browser, global variables declared with `var` become the property of the global object: -4. And, a minor thing, but still: the value of `this` in the global scope is `window`. +```js run untrusted refresh +var gVar = 5; - ```js untrusted run no-strict refresh - alert(this); // window - ``` - -Why was it made like this? At the time of the language creation, the idea to merge multiple aspects into a single `window` object was to "make things simple". But since then many things changed. Tiny scripts became big applications that require proper architecture. - -Is it good that different scripts (possibly from different sources) see variables of each other? - -No, it's not, because it may lead to naming conflicts: the same variable name can be used in two scripts for different purposes, so they will conflict with each other. - -As of now, the multi-purpose `window` is considered a design mistake in the language. - -Luckily, there's a "road out of hell", called "JavaScript modules". - -If we set `type="module"` attribute on a ` - ``` +alert(window.gVar); // 5 (became a property of the global object) +``` -- Two modules that do not see variables of each other: +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use JavaScript modules where such thing doesn't happen. We'll cover them later in the chapter [](info:modules). - ```html run - +Also, more modern variable declarations `let` and `const` do not exhibit such behavior at all: - - ``` +```js run untrusted refresh +let gLet = 5; -- And, the last minor thing, the top-level value of `this` in a module is `undefined` (why should it be `window` anyway?): +alert(window.gLet); // undefined (doesn't become a property of the global object) +``` - ```html run - - ``` +If a value is so important that you'd like to make it available globally, write it directly as a property: -**Using ` + + Press the "Start" to begin. +
+ + "Stop" to finish. diff --git a/5-network/09-server-sent-events/eventsource.view/server.js b/5-network/09-server-sent-events/eventsource.view/server.js new file mode 100644 index 000000000..34c7b1253 --- /dev/null +++ b/5-network/09-server-sent-events/eventsource.view/server.js @@ -0,0 +1,47 @@ +let http = require('http'); +let url = require('url'); +let querystring = require('querystring'); + +function onDigits(req, res) { + res.writeHead(200, { + 'Content-Type': 'text/event-stream; charset=utf-8', + 'Cache-Control': 'no-cache' + }); + + let i = 0; + + let timer = setInterval(write, 1000); + write(); + + function write() { + i++; + + if (i == 4) { + res.write('event: bye\ndata: bye-bye\n\n'); + clearInterval(timer); + res.end(); + return; + } + + res.write('data: ' + i + '\n\n'); + + } +} + +function accept(req, res) { + + if (req.url == '/digits') { + onDigits(req, res); + return; + } + + fileServer.serve(req, res); + +} + + +if (!module.parent) { + http.createServer(accept).listen(8080); +} else { + exports.accept = accept; +} diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index ecf3f3566..4194bd15a 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -57,7 +57,7 @@ openRequest.onupgradeneeded = function() { }; openRequest.onerror = function() { - console.error("Error", openResult.error); + console.error("Error", openRequest.error); }; openRequest.onsuccess = function() { @@ -107,16 +107,18 @@ IndexedDB uses the [standard serialization algorithm](https://www.w3.org/TR/html An example of object that can't be stored: an object with circular references. Such objects are not serializable. `JSON.stringify` also fails for such objects. -**There must be an unique `key` for every value in the store.** +**There must be a unique `key` for every value in the store.** A key must have a type one of: number, date, string, binary, or array. It's an unique identifier: we can search/remove/update values by the key. ![](indexeddb-structure.png) -As we'll see very soon, we can provide a key when we add an value to the store, similar to `localStorage`. But when we store objects, IndexedDB allows to setup an object property as the key, that's much more convenient. Or we can auto-generate keys. + +As we'll see very soon, we can provide a key when we add a value to the store, similar to `localStorage`. But when we store objects, IndexedDB allows to setup an object property as the key, that's much more convenient. Or we can auto-generate keys. But we need to create an object store first. + The syntax to create an object store: ```js db.createObjectStore(name[, keyOptions]); @@ -194,7 +196,7 @@ db.transaction(store[, type]); - `readonly` -- can only read, the default. - `readwrite` -- can only read and write the data, but not create/remove/alter object stores. -There'is also `versionchange` transaction type: such transactions can do everything, but we can't create them manually. IndexedDB automatically creates a `versionchange` transaction when opening the database, for `updateneeded` handler. That's why it's a single place where we can update the database structure, create/remove object stores. +There's also `versionchange` transaction type: such transactions can do everything, but we can't create them manually. IndexedDB automatically creates a `versionchange` transaction when opening the database, for `updateneeded` handler. That's why it's a single place where we can update the database structure, create/remove object stores. ```smart header="Why there exist different types of transactions?" Performance is the reason why transactions need to be labeled either `readonly` and `readwrite`. @@ -462,7 +464,7 @@ objectStore.createIndex(name, keyPath, [options]); - **`keyPath`** -- path to the object field that the index should track (we're going to search by that field), - **`option`** -- an optional object with properties: - **`unique`** -- if true, then there may be only one object in the store with the given value at the `keyPath`. The index will enforce that by generating an error if we try to add a duplicate. - - **`multiEntry`** -- only used if there value on `keyPath` is an array. In that case, by default, the index will treat the whole array as the key. But if `multiEntry` is true, then the index will keep a list of store objects for each value in that array. So array members become index keys. + - **`multiEntry`** -- only used if the value on `keyPath` is an array. In that case, by default, the index will treat the whole array as the key. But if `multiEntry` is true, then the index will keep a list of store objects for each value in that array. So array members become index keys. In our example, we store books keyed by `id`. @@ -611,7 +613,7 @@ Whether there are more values matching the cursor or not -- `onsuccess` gets cal In the example above the cursor was made for the object store. -But we also can make a cursor over an index. As we remember, indexes allow to search by an object field. Cursors over indexes to precisely the same as over object stores -- they save memory by returning one value at a timee. +But we also can make a cursor over an index. As we remember, indexes allow to search by an object field. Cursors over indexes to precisely the same as over object stores -- they save memory by returning one value at a time. For cursors over indexes, `cursor.key` is the index key (e.g. price), and we should use `cursor.primaryKey` property the object key: @@ -685,7 +687,9 @@ window.addEventListener('unhandledrejection', event => { ### "Inactive transaction" pitfall -A we know already, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put an *macrotask* like `fetch` in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it would fail. + +As we already know, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put a *macrotask* like `fetch` in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it would fail. + For a promise wrapper and `async/await` the situation is the same. @@ -712,7 +716,7 @@ The workaround is same as when working with native IndexedDB: either make a new Internally, the wrapper performs a native IndexedDB request, adding `onerror/onsuccess` to it, and returns a promise that rejects/resolves with the result. -That works most fine of the time. The examples are at the lib page . +That works fine most of the time. The examples are at the lib page . In few rare cases, when we need the original `request` object, we can access it as `promise.request` property of the promise: diff --git a/8-web-components/7-shadow-dom-events/article.md b/8-web-components/7-shadow-dom-events/article.md index 14e310a55..4199d104d 100644 --- a/8-web-components/7-shadow-dom-events/article.md +++ b/8-web-components/7-shadow-dom-events/article.md @@ -2,9 +2,9 @@ The idea behind shadow tree is to encapsulate internal implementation details of a component. -Let's say, a click event happens inside a shadow DOM of `` component. But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library or so. +Let's say, a click event happens inside a shadow DOM of `` component. But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library. -So, to keep things simple, the browser *retargets* the event. +So, to keep the details encapsulated, the browser *retargets* the event. **Events that happen in shadow DOM have the host element as the target, when caught outside of the component.** @@ -39,7 +39,7 @@ Event retargeting is a great thing to have, because the outer document doesn't h **Retargeting does not occur if the event occurs on a slotted element, that physically lives in the light DOM.** -For example, if a user clicks on `` in the example below, the event target is exactly this element, for both shadow and light handlers: +For example, if a user clicks on `` in the example below, the event target is exactly this `span` element, for both shadow and light handlers: ```html run autorun="no-epub" untrusted height=60 @@ -75,7 +75,7 @@ For purposes of event bubbling, flattened DOM is used. So, if we have a slotted element, and an event occurs somewhere inside it, then it bubbles up to the `` and upwards. -The full path to the original event target, with all the shadow root elements, can be obtained using `event.composedPath()`. As we can see from the name of the method, that path is taken after the composition. +The full path to the original event target, with all the shadow elements, can be obtained using `event.composedPath()`. As we can see from the name of the method, that path is taken after the composition. In the example above, the flattened DOM is: @@ -119,7 +119,7 @@ All touch events and pointer events also have `composed: true`. There are some events that have `composed: false` though: -- `mouseenter`, `mouseleave` (they also do not bubble), +- `mouseenter`, `mouseleave` (they do not bubble at all), - `load`, `unload`, `abort`, `error`, - `select`, - `slotchange`. @@ -189,4 +189,4 @@ These events can be caught only on elements within the same DOM. If we dispatch a `CustomEvent`, then we should explicitly set `composed: true`. -Please note that in case of nested components, composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host. Then it's out of the shadow DOM already. +Please note that in case of nested components, one shadow DOM may be nested into another. In that case composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host and set `composed: false`. Then it's out of the component shadow DOM, but won't bubble up to higher-level DOM. diff --git a/9-regular-expressions/14-regexp-lookahead-lookbehind/article.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/article.md index 376d52d66..ec13cedc5 100644 --- a/9-regular-expressions/14-regexp-lookahead-lookbehind/article.md +++ b/9-regular-expressions/14-regexp-lookahead-lookbehind/article.md @@ -60,9 +60,9 @@ alert( str.match(/(?. +======= +♥ +Ilya Kantor @iliakan +>>>>>>> 027933531e121650120f7e8385f691de99af12d2 diff --git a/css.md b/css.md new file mode 100644 index 000000000..6a6316e69 --- /dev/null +++ b/css.md @@ -0,0 +1,4 @@ + +# CSS for JS developers + +- Outline diff --git a/figures.sketch b/figures.sketch index bebfcd3ec..ca923477e 100644 Binary files a/figures.sketch and b/figures.sketch differ