Merge branch 'master' into patch-1
This commit is contained in:
commit
246c6c4340
54 changed files with 283 additions and 201 deletions
|
@ -68,7 +68,7 @@ Examples of such restrictions include:
|
|||
Modern browsers allow it to work with files, but the access is limited and only provided if the user does certain actions, like "dropping" a file into a browser window or selecting it via an `<input>` tag.
|
||||
|
||||
There are ways to interact with camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency).
|
||||
- Different tabs/windows generally do not know about each other. Sometimes they do; for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other if they come from different sites (from a different domain, protocol or port).
|
||||
- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other if they come from different sites (from a different domain, protocol or port).
|
||||
|
||||
This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and contain a special JavaScript code that handles it. We'll cover that in the tutorial.
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ To see errors and get a lot of other useful information about scripts, "develope
|
|||
|
||||
Most developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific.
|
||||
|
||||
Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands.
|
||||
Developer tools are potent, they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands.
|
||||
|
||||
## Google Chrome
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ So first, let's see how we attach a script to a webpage. For server-side environ
|
|||
|
||||
## The "script" tag
|
||||
|
||||
JavaScript programs can be inserted into any part of an HTML document with the help of the `<script>` tag.
|
||||
JavaScript programs can be inserted almost anywhere into an HTML document using the `<script>` tag.
|
||||
|
||||
For instance:
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ Here's an example of an `.eslintrc` file:
|
|||
},
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"indent": ["warning", 2]
|
||||
"indent": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -23,7 +23,7 @@ Actually, there are two parts in Babel:
|
|||
|
||||
2. Second, the polyfill.
|
||||
|
||||
New language features may include new built-in functions and syntax constructs.
|
||||
New language features may include not only syntax constructs, but also built-in functions.
|
||||
The transpiler rewrites the code, transforming syntax constructs into older ones. But as for new built-in functions, we need to implement them. JavaScript is a highly dynamic language, scripts may add/modify any functions, so that they behave according to the modern standard.
|
||||
|
||||
A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations.
|
||||
|
|
|
@ -92,30 +92,6 @@ let user = {
|
|||
```
|
||||
That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.
|
||||
|
||||
````smart header="Object with const can be changed"
|
||||
Please note: an object declared as `const` *can* be modified.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
const user = {
|
||||
name: "John"
|
||||
};
|
||||
|
||||
*!*
|
||||
user.name = "Pete"; // (*)
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Pete
|
||||
```
|
||||
|
||||
It might seem that the line `(*)` would cause an error, but no. The `const` fixes the value of `user`, but not its contents.
|
||||
|
||||
The `const` would give an error only if we try to set `user=...` as a whole.
|
||||
|
||||
There's another way to make constant object properties, we'll cover it later in the chapter <info:property-descriptors>.
|
||||
````
|
||||
|
||||
## Square brackets
|
||||
|
||||
For multiword properties, the dot access doesn't work:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Object references and copying
|
||||
|
||||
One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", as opposed to primitive values: strings, numbers, booleans, etc -- that are always copied "as a whole value".
|
||||
One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc -- are always copied "as a whole value".
|
||||
|
||||
That's easy to understand if we look a bit "under a cover" of what happens when we copy a value.
|
||||
That's easy to understand if we look a bit under the hood of what happens when we copy a value.
|
||||
|
||||
Let's start with a primitive, such as a string.
|
||||
|
||||
|
@ -13,7 +13,7 @@ let message = "Hello!";
|
|||
let phrase = message;
|
||||
```
|
||||
|
||||
As a result we have two independent variables, each one is storing the string `"Hello!"`.
|
||||
As a result we have two independent variables, each one storing the string `"Hello!"`.
|
||||
|
||||

|
||||
|
||||
|
@ -21,9 +21,9 @@ Quite an obvious result, right?
|
|||
|
||||
Objects are not like that.
|
||||
|
||||
**A variable assigned to an object stores not the object itself, but its "address in memory", in other words "a reference" to it.**
|
||||
**A variable assigned to an object stores not the object itself, but its "address in memory" -- in other words "a reference" to it.**
|
||||
|
||||
Let's look at an example of such variable:
|
||||
Let's look at an example of such a variable:
|
||||
|
||||
```js
|
||||
let user = {
|
||||
|
@ -37,13 +37,13 @@ And here's how it's actually stored in memory:
|
|||
|
||||
The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it.
|
||||
|
||||
We may think of an object variable, such as `user`, as of a sheet of paper with the address.
|
||||
We may think of an object variable, such as `user`, as like a sheet of paper with the address of the object on it.
|
||||
|
||||
When we perform actions with the object, e.g. take a property `user.name`, JavaScript engine looks into that address and performs the operation on the actual object.
|
||||
When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object.
|
||||
|
||||
Now here's why it's important.
|
||||
|
||||
**When an object variable is copied -- the reference is copied, the object is not duplicated.**
|
||||
**When an object variable is copied, the reference is copied, but the object itself is not duplicated.**
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -53,13 +53,13 @@ let user = { name: "John" };
|
|||
let admin = user; // copy the reference
|
||||
```
|
||||
|
||||
Now we have two variables, each one with the reference to the same object:
|
||||
Now we have two variables, each storing a reference to the same object:
|
||||
|
||||

|
||||
|
||||
As you can see, there's still one object, now with two variables that reference it.
|
||||
As you can see, there's still one object, but now with two variables that reference it.
|
||||
|
||||
We can use any variable to access the object and modify its contents:
|
||||
We can use either variable to access the object and modify its contents:
|
||||
|
||||
```js run
|
||||
let user = { name: 'John' };
|
||||
|
@ -73,8 +73,7 @@ admin.name = 'Pete'; // changed by the "admin" reference
|
|||
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
|
||||
```
|
||||
|
||||
|
||||
It's just as if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use another key (`user`) we can see changes.
|
||||
It's as if we had a cabinet with two keys and used one of them (`admin`) to get into it and make changes. Then, if we later use another key (`user`), we are still opening the same cabinet and can access the changed contents.
|
||||
|
||||
## Comparison by reference
|
||||
|
||||
|
@ -99,7 +98,7 @@ let b = {}; // two independent objects
|
|||
alert( a == b ); // false
|
||||
```
|
||||
|
||||
For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely, usually they appear as a result of a programming mistake.
|
||||
For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake.
|
||||
|
||||
## Cloning and merging, Object.assign
|
||||
|
||||
|
@ -107,7 +106,7 @@ So, copying an object variable creates one more reference to the same object.
|
|||
|
||||
But what if we need to duplicate an object? Create an independent copy, a clone?
|
||||
|
||||
That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time.
|
||||
That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need -- copying by reference is good most of the time.
|
||||
|
||||
But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
|
||||
|
||||
|
@ -226,13 +225,37 @@ user.sizes.width++; // change a property from one place
|
|||
alert(clone.sizes.width); // 51, see the result from the other one
|
||||
```
|
||||
|
||||
To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning".
|
||||
To fix that, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning".
|
||||
|
||||
We can use recursion to implement it. Or, not to reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
|
||||
We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
|
||||
|
||||
````smart header="Const objects can be modified"
|
||||
An important side effect of storing objects as references is that an object declared as `const` *can* be modified.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
const user = {
|
||||
name: "John"
|
||||
};
|
||||
|
||||
*!*
|
||||
user.name = "Pete"; // (*)
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Pete
|
||||
```
|
||||
|
||||
It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change.
|
||||
|
||||
In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
|
||||
|
||||
That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter <info:property-descriptors>.
|
||||
````
|
||||
|
||||
## Summary
|
||||
|
||||
Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object.
|
||||
Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself.
|
||||
|
||||
All operations via copied references (like adding/removing properties) are performed on the same single object.
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ Simply put, "reachable" values are those that are accessible or usable somehow.
|
|||
|
||||
2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references.
|
||||
|
||||
For instance, if there's an object in a global variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow.
|
||||
For instance, if there's an object in a global variable, and that object has a property referencing another object, *that* object is considered reachable. And those that it references are also reachable. Detailed examples to follow.
|
||||
|
||||
There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable.
|
||||
|
||||
|
|
|
@ -9,44 +9,80 @@ The optional chaining `?.` is a safe way to access nested object properties, eve
|
|||
|
||||
If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common.
|
||||
|
||||
As an example, let's consider objects for user data. Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them.
|
||||
As an example, let's say we have `user` objects that hold the information about our users.
|
||||
|
||||
In such case, when we attempt to get `user.address.street`, we'll get an error:
|
||||
Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them.
|
||||
|
||||
In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error:
|
||||
|
||||
```js run
|
||||
let user = {}; // the user without "address" property
|
||||
let user = {}; // a user without "address" property
|
||||
|
||||
alert(user.address.street); // Error!
|
||||
```
|
||||
|
||||
That's the expected result, JavaScript works like this, but many practical cases we'd prefer to get `undefined` instead of an error (meaning "no street").
|
||||
That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error.
|
||||
|
||||
...And another example. In the web development, we may need to get an information about an element on the page, that sometimes doesn't exist:
|
||||
In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street").
|
||||
|
||||
...And another example. In the web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element.
|
||||
|
||||
```js run
|
||||
// Error if the result of querySelector(...) is null
|
||||
let html = document.querySelector('.my-element').innerHTML;
|
||||
// document.querySelector('.elem') is null if there's no element
|
||||
let html = document.querySelector('.elem').innerHTML; // error if it's null
|
||||
```
|
||||
|
||||
Before `?.` appeared in the language, the `&&` operator was used to work around that.
|
||||
Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
|
||||
|
||||
For example:
|
||||
How can we do this?
|
||||
|
||||
The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this:
|
||||
|
||||
```js
|
||||
let user = {};
|
||||
|
||||
alert(user.address ? user.address.street : undefined);
|
||||
```
|
||||
|
||||
It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required.
|
||||
|
||||
E.g. let's try getting `user.address.street.name`.
|
||||
|
||||
We need to check both `user.address` and `user.address.street`:
|
||||
|
||||
```js
|
||||
let user = {}; // user has no address
|
||||
|
||||
alert(user.address ? user.address.street ? user.address.street.name : null : null);
|
||||
```
|
||||
|
||||
That's just awful, one may even have problems understanding such code.
|
||||
|
||||
Don't even care to, as there's a better way to write it, using the `&&` operator:
|
||||
|
||||
```js run
|
||||
let user = {}; // user has no address
|
||||
|
||||
alert( user && user.address && user.address.street ); // undefined (no error)
|
||||
alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)
|
||||
```
|
||||
|
||||
AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but is cumbersome to write.
|
||||
AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal.
|
||||
|
||||
As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times.
|
||||
|
||||
That's why the optional chaining `?.` was added to the language. To solve this problem once and for all!
|
||||
|
||||
## Optional chaining
|
||||
|
||||
The optional chaining `?.` stops the evaluation and returns `undefined` if the part before `?.` is `undefined` or `null`.
|
||||
The optional chaining `?.` stops the evaluation if the part before `?.` is `undefined` or `null` and returns that part.
|
||||
|
||||
**Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.**
|
||||
|
||||
Here's the safe way to access `user.address.street`:
|
||||
In other words, `value?.prop`:
|
||||
- is the same as `value.prop` if `value` exists,
|
||||
- otherwise (when `value` is `undefined/null`) it returns `undefined`.
|
||||
|
||||
Here's the safe way to access `user.address.street` using `?.`:
|
||||
|
||||
```js run
|
||||
let user = {}; // user has no address
|
||||
|
@ -54,6 +90,8 @@ let user = {}; // user has no address
|
|||
alert( user?.address?.street ); // undefined (no error)
|
||||
```
|
||||
|
||||
The code is short and clean, there's no duplication at all.
|
||||
|
||||
Reading the address with `user?.address` works even if `user` object doesn't exist:
|
||||
|
||||
```js run
|
||||
|
@ -65,14 +103,12 @@ alert( user?.address.street ); // undefined
|
|||
|
||||
Please note: the `?.` syntax makes optional the value before it, but not any further.
|
||||
|
||||
In the example above, `user?.` allows only `user` to be `null/undefined`.
|
||||
|
||||
On the other hand, if `user` does exist, then it must have `user.address` property, otherwise `user?.address.street` gives an error at the second dot.
|
||||
E.g. in `user?.address.street.name` the `?.` allows `user` to be `null/undefined`, but it's all it does. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`.
|
||||
|
||||
```warn header="Don't overuse the optional chaining"
|
||||
We should use `?.` only where it's ok that something doesn't exist.
|
||||
|
||||
For example, if according to our coding logic `user` object must be there, but `address` is optional, then `user.address?.street` would be better.
|
||||
For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
|
||||
|
||||
So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.
|
||||
```
|
||||
|
@ -84,7 +120,7 @@ If there's no variable `user` at all, then `user?.anything` triggers an error:
|
|||
// ReferenceError: user is not defined
|
||||
user?.address;
|
||||
```
|
||||
There must be a declaration (e.g. `let/const/var user`). The optional chaining works only for declared variables.
|
||||
The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables.
|
||||
````
|
||||
|
||||
## Short-circuiting
|
||||
|
@ -113,17 +149,20 @@ For example, `?.()` is used to call a function that may not exist.
|
|||
In the code below, some of our users have `admin` method, and some don't:
|
||||
|
||||
```js run
|
||||
let user1 = {
|
||||
let userAdmin = {
|
||||
admin() {
|
||||
alert("I am admin");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let user2 = {};
|
||||
let userGuest = {};
|
||||
|
||||
*!*
|
||||
user1.admin?.(); // I am admin
|
||||
user2.admin?.();
|
||||
userAdmin.admin?.(); // I am admin
|
||||
*/!*
|
||||
|
||||
*!*
|
||||
userGuest.admin?.(); // nothing (no such method)
|
||||
*/!*
|
||||
```
|
||||
|
||||
|
|
|
@ -406,7 +406,7 @@ A few examples:
|
|||
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
|
||||
```
|
||||
|
||||
There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object.
|
||||
There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math).
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ importance: 4
|
|||
|
||||
# Filter range
|
||||
|
||||
Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements between `a` and `b` in it and returns an array of them.
|
||||
Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array.
|
||||
|
||||
The function should not modify the array. It should return the new array.
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ let array = [ john ];
|
|||
john = null; // overwrite the reference
|
||||
|
||||
*!*
|
||||
// john is stored inside the array, so it won't be garbage-collected
|
||||
// the object previously referenced by john is stored inside the array
|
||||
// therefore it won't be garbage-collected
|
||||
// we can get it as array[0]
|
||||
*/!*
|
||||
```
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
By definition, a factorial is `n!` can be written as `n * (n-1)!`.
|
||||
By definition, a factorial `n!` can be written as `n * (n-1)!`.
|
||||
|
||||
In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`.
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ We can sketch it as:
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
That's when the function starts to execute. The condition `n == 1` is false, so the flow continues into the second branch of `if`:
|
||||
That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`:
|
||||
|
||||
```js run
|
||||
function pow(x, n) {
|
||||
|
@ -188,7 +188,7 @@ The new current execution context is on top (and bold), and previous remembered
|
|||
When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped.
|
||||
|
||||
```smart
|
||||
Here in the picture we use the word "line", as our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`.
|
||||
Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`.
|
||||
|
||||
So it would be more precise to say that the execution resumes "immediately after the subcall".
|
||||
```
|
||||
|
|
|
@ -82,7 +82,7 @@ function sayHi() {
|
|||
}
|
||||
|
||||
sayHi();
|
||||
alert(phrase); // Error: phrase is not defined (Check the Developer Console)
|
||||
alert(phrase); // Error: phrase is not defined
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -279,7 +279,7 @@ In all the above cases we declare a Function Expression and run it immediately.
|
|||
|
||||
There are two main differences of `var` compared to `let/const`:
|
||||
|
||||
1. `var` variables have no block scope; their visibility is scoped to current function, or global, if declared outside function.
|
||||
1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function.
|
||||
2. `var` declarations are processed at function start (script start for globals).
|
||||
|
||||
There's one more very minor difference related to the global object, that we'll cover in the next chapter.
|
||||
|
|
|
@ -5,7 +5,7 @@ The global object provides variables and functions that are available anywhere.
|
|||
|
||||
In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name.
|
||||
|
||||
Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers.
|
||||
Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers.
|
||||
|
||||
We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead.
|
||||
|
||||
|
@ -25,6 +25,8 @@ var gVar = 5;
|
|||
alert(window.gVar); // 5 (became a property of the global object)
|
||||
```
|
||||
|
||||
The same effect have function declarations (statements with `function` keyword in the main code flow, not function expressions).
|
||||
|
||||
Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen.
|
||||
|
||||
If we used `let` instead, such thing wouldn't happen:
|
||||
|
|
|
@ -347,7 +347,7 @@ If the function is declared as a Function Expression (not in the main code flow)
|
|||
|
||||
Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.
|
||||
|
||||
They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
|
||||
They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
|
||||
|
||||
|
||||
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
|
||||
|
|
|
@ -92,7 +92,7 @@ What if it could access the outer variables?
|
|||
|
||||
The problem is that before JavaScript is published to production, it's compressed using a *minifier* -- a special program that shrinks code by removing extra comments, spaces and -- what's important, renames local variables into shorter ones.
|
||||
|
||||
For instance, if a function has `let userName`, minifier replaces it `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace.
|
||||
For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace.
|
||||
|
||||
So if `new Function` had access to outer variables, it would be unable to find renamed `userName`.
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
|
|||
```smart header="Time goes on while `alert` is shown"
|
||||
In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showing `alert/confirm/prompt`.
|
||||
|
||||
So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds.
|
||||
So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds.
|
||||
```
|
||||
|
||||
## Nested setTimeout
|
||||
|
@ -281,7 +281,7 @@ The similar thing happens if we use `setInterval` instead of `setTimeout`: `setI
|
|||
|
||||
That limitation comes from ancient times and many scripts rely on it, so it exists for historical reasons.
|
||||
|
||||
For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html) for Node.js. So this note is browser-specific.
|
||||
For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific.
|
||||
````
|
||||
|
||||
## Summary
|
||||
|
@ -290,7 +290,7 @@ For server-side JavaScript, that limitation does not exist, and there exist othe
|
|||
- To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`.
|
||||
- Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely.
|
||||
- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete".
|
||||
- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
|
||||
- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
|
||||
|
||||
Please note that all scheduling methods do not *guarantee* the exact delay.
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named
|
|||
|
||||

|
||||
|
||||
The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it.
|
||||
When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it.
|
||||
|
||||
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
|
||||
|
||||
|
@ -27,19 +27,11 @@ let rabbit = {
|
|||
};
|
||||
|
||||
*!*
|
||||
rabbit.__proto__ = animal;
|
||||
rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal
|
||||
*/!*
|
||||
```
|
||||
|
||||
```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
|
||||
Please note that `__proto__` is *not the same* as `[[Prototype]]`. It's a getter/setter for it.
|
||||
|
||||
It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
|
||||
|
||||
By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples.
|
||||
```
|
||||
|
||||
If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`.
|
||||
Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit)
|
|||
|
||||

|
||||
|
||||
Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`.
|
||||
|
||||
There are only two limitations:
|
||||
|
||||
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
|
||||
|
@ -137,6 +131,19 @@ There are only two limitations:
|
|||
|
||||
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
|
||||
|
||||
|
||||
```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
|
||||
It's a common mistake of novice developers not to know the difference between these two.
|
||||
|
||||
Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language.
|
||||
|
||||
The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later.
|
||||
|
||||
By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it.
|
||||
|
||||
As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples.
|
||||
```
|
||||
|
||||
## Writing doesn't use prototype
|
||||
|
||||
The prototype is only used for reading properties.
|
||||
|
@ -198,8 +205,8 @@ alert(admin.fullName); // John Smith (*)
|
|||
// setter triggers!
|
||||
admin.fullName = "Alice Cooper"; // (**)
|
||||
|
||||
alert(admin.fullName); // Alice Cooper , state of admin modified
|
||||
alert(user.fullName); // John Smith , state of user protected
|
||||
alert(admin.fullName); // Alice Cooper, state of admin modified
|
||||
alert(user.fullName); // John Smith, state of user protected
|
||||
```
|
||||
|
||||
Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called.
|
||||
|
|
|
@ -176,8 +176,8 @@ alert(Object.keys(chineseDictionary)); // hello,bye
|
|||
Modern methods to set up and directly access the prototype are:
|
||||
|
||||
- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors.
|
||||
- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
|
||||
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
|
||||
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
|
||||
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
|
||||
|
||||
The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys into an object. Just because a user may enter `"__proto__"` as the key, and there'll be an error, with hopefully light, but generally unpredictable consequences.
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@ importance: 5
|
|||
|
||||
# Rewrite to class
|
||||
|
||||
The `Clock` class is written in functional style. Rewrite it the "class" syntax.
|
||||
The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax.
|
||||
|
||||
P.S. The clock ticks in the console, open it to see.
|
||||
|
|
|
@ -218,7 +218,7 @@ function makeClass(phrase) {
|
|||
return class {
|
||||
sayHi() {
|
||||
alert(phrase);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -76,8 +76,8 @@ For instance, a function call that generates the parent class:
|
|||
```js run
|
||||
function f(phrase) {
|
||||
return class {
|
||||
sayHi() { alert(phrase) }
|
||||
}
|
||||
sayHi() { alert(phrase); }
|
||||
};
|
||||
}
|
||||
|
||||
*!*
|
||||
|
@ -300,7 +300,7 @@ Consider this example:
|
|||
|
||||
```js run
|
||||
class Animal {
|
||||
name = 'animal'
|
||||
name = 'animal';
|
||||
|
||||
constructor() {
|
||||
alert(this.name); // (*)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.
|
||||
|
||||
Such a check may be necessary in many cases. Here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type.
|
||||
Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type.
|
||||
|
||||
## The instanceof operator [#ref-instanceof]
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ loadScript('/my/script.js', function(script) {
|
|||
});
|
||||
*/!*
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
@ -223,7 +223,7 @@ loadScript('1.js', function(error, script) {
|
|||
});
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
@ -256,7 +256,7 @@ loadScript('1.js', function(error, script) {
|
|||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
-->
|
||||
|
@ -296,7 +296,7 @@ function step3(error, script) {
|
|||
} else {
|
||||
// ...continue after all scripts are loaded (*)
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See? It does the same, and there's no deep nesting now because we made every action a separate top-level function.
|
||||
|
|
|
@ -265,7 +265,7 @@ fetch('/article/promise-chaining/user.json')
|
|||
|
||||
Now let's do something with the loaded user.
|
||||
|
||||
For instance, we can make one more requests to GitHub, load the user profile and show the avatar:
|
||||
For instance, we can make one more request to GitHub, load the user profile and show the avatar:
|
||||
|
||||
```js run
|
||||
// Make a request for user.json
|
||||
|
|
|
@ -176,15 +176,14 @@ So for each promise we get its status and `value/error`.
|
|||
If the browser doesn't support `Promise.allSettled`, it's easy to polyfill:
|
||||
|
||||
```js
|
||||
if(!Promise.allSettled) {
|
||||
Promise.allSettled = function(promises) {
|
||||
return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
|
||||
status: 'fulfilled',
|
||||
value
|
||||
}), reason => ({
|
||||
status: 'rejected',
|
||||
reason
|
||||
}))));
|
||||
if (!Promise.allSettled) {
|
||||
const rejectHandler = reason => ({ status: 'rejected', reason });
|
||||
|
||||
const resolveHandler = value => ({ status: 'fulfilled', value });
|
||||
|
||||
Promise.allSettled = function (promises) {
|
||||
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
|
||||
return Promise.all(convertedPromises);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
Such transformations are often required in real-life, as many functions and libraries are callback-based. But promises are more convenient, so it makes sense to promisify them.
|
||||
|
||||
For better understanding, let's see an example.
|
||||
|
||||
For instance, we have `loadScript(src, callback)` from the chapter <info:callbacks>.
|
||||
|
||||
```js run
|
||||
|
@ -21,35 +23,42 @@ function loadScript(src, callback) {
|
|||
// loadScript('path/script.js', (err, script) => {...})
|
||||
```
|
||||
|
||||
Let's promisify it. The new `loadScriptPromise(src)` function achieves the same result, but it accepts only `src` (no `callback`) and returns a promise.
|
||||
The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before.
|
||||
|
||||
Let's promisify it.
|
||||
|
||||
We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks.
|
||||
|
||||
In other words, we pass it only `src` (no `callback`) and get a promise in return, that resolves with `script` when the load is successful, and rejects with the error otherwise.
|
||||
|
||||
Here it is:
|
||||
```js
|
||||
let loadScriptPromise = function(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
loadScript(src, (err, script) => {
|
||||
if (err) reject(err)
|
||||
if (err) reject(err);
|
||||
else resolve(script);
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// usage:
|
||||
// loadScriptPromise('path/script.js').then(...)
|
||||
```
|
||||
|
||||
Now `loadScriptPromise` fits well in promise-based code.
|
||||
As we can see, the new function is a wrapper around the original `loadScript` function. It calls it providing its own callback that translates to promise `resolve/reject`.
|
||||
|
||||
As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`.
|
||||
Now `loadScriptPromise` fits well in promise-based code. If we like promises more than callbacks (and soon we'll see more reasons for that), then we will use it instead.
|
||||
|
||||
In practice we'll probably need to promisify many functions, so it makes sense to use a helper. We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.
|
||||
In practice we may need to promisify more than one function, so it makes sense to use a helper.
|
||||
|
||||
That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback:
|
||||
We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.
|
||||
|
||||
```js
|
||||
function promisify(f) {
|
||||
return function (...args) { // return a wrapper-function
|
||||
return function (...args) { // return a wrapper-function (*)
|
||||
return new Promise((resolve, reject) => {
|
||||
function callback(err, result) { // our custom callback for f
|
||||
function callback(err, result) { // our custom callback for f (**)
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
@ -62,18 +71,25 @@ function promisify(f) {
|
|||
f.call(this, ...args); // call the original function
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// usage:
|
||||
let loadScriptPromise = promisify(loadScript);
|
||||
loadScriptPromise(...).then(...);
|
||||
```
|
||||
|
||||
Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case.
|
||||
The code may look a bit complex, but it's essentially the same that we wrote above, while promisifying `loadScript` function.
|
||||
|
||||
A call to `promisify(f)` returns a wrapper around `f` `(*)`. That wrapper returns a promise and forwards the call to the original `f`, tracking the result in the custom callback `(**)`.
|
||||
|
||||
Here, `promisify` assumes that the original function expects a callback with exactly two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case.
|
||||
|
||||
But what if the original `f` expects a callback with more arguments `callback(err, res1, res2, ...)`?
|
||||
|
||||
Here's a more advanced version of `promisify`: if called as `promisify(f, true)`, the promise result will be an array of callback results `[res1, res2, ...]`:
|
||||
We can improve our helper. Let's make a more advanced version of `promisify`.
|
||||
|
||||
- When called as `promisify(f)` it should work similar to the version above.
|
||||
- When called as `promisify(f, true)`, it should return the promise that resolves with the array of callback results. That's exactly for callbacks with many arguments.
|
||||
|
||||
```js
|
||||
// promisify(f, true) to get array of results
|
||||
|
@ -94,13 +110,15 @@ function promisify(f, manyArgs = false) {
|
|||
f.call(this, ...args);
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// usage:
|
||||
f = promisify(f, true);
|
||||
f(...).then(arrayOfResults => ..., err => ...)
|
||||
f(...).then(arrayOfResults => ..., err => ...);
|
||||
```
|
||||
|
||||
As you can see it's essentially the same as above, but `resolve` is called with only one or all arguments depending on whether `manyArgs` is truthy.
|
||||
|
||||
For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions manually without using the helper.
|
||||
|
||||
There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that.
|
||||
|
|
|
@ -12,7 +12,7 @@ function loadJson(url) {
|
|||
} else {
|
||||
throw new Error(response.status);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
loadJson('no-such-user.json')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Rewrite "rethrow" with async/await
|
||||
|
||||
Below you can find the "rethrow" example from the chapter <info:promise-chaining>. Rewrite it using `async/await` instead of `.then/catch`.
|
||||
Below you can find the "rethrow" example. Rewrite it using `async/await` instead of `.then/catch`.
|
||||
|
||||
And get rid of the recursion in favour of a loop in `demoGithubUser`: with `async/await` that becomes easy to do.
|
||||
|
||||
|
@ -22,7 +22,7 @@ function loadJson(url) {
|
|||
} else {
|
||||
throw new HttpError(response);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Ask for a user name until github returns a valid user
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Call async from non-async
|
||||
|
||||
We have a "regular" function. How to call `async` from it and use its result?
|
||||
We have a "regular" function called `f`. How can you call the `async` function `wait()` and use its result inside of `f`?
|
||||
|
||||
```js
|
||||
async function wait() {
|
||||
|
@ -11,7 +11,7 @@ async function wait() {
|
|||
}
|
||||
|
||||
function f() {
|
||||
// ...what to write here?
|
||||
// ...what should you write here?
|
||||
// we need to call async wait() and wait to get 10
|
||||
// remember, we can't use "await"
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ class Thenable {
|
|||
// resolve with this.num*2 after 1000ms
|
||||
setTimeout(() => resolve(this.num * 2), 1000); // (*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function f() {
|
||||
// waits for 1 second, then result becomes 2
|
||||
|
|
|
@ -301,7 +301,7 @@ Now values come with a delay of 1 second between them.
|
|||
```smart
|
||||
Technically, we can add both `Symbol.iterator` and `Symbol.asyncIterator` to the object, so it's both synchronously (`for..of`) and asynchronously (`for await..of`) iterable.
|
||||
|
||||
In practice though, that would be an weird thing to do.
|
||||
In practice though, that would be a weird thing to do.
|
||||
```
|
||||
|
||||
## Real-life example: paginated data
|
||||
|
@ -363,7 +363,7 @@ More explanations about how it works:
|
|||
- The initial URL is `https://api.github.com/repos/<repo>/commits`, and the next page will be in the `Link` header of the response.
|
||||
- The `fetch` method allows us to supply authorization and other headers if needed -- here GitHub requires `User-Agent`.
|
||||
2. The commits are returned in JSON format.
|
||||
3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regular expression for that.
|
||||
3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regular expression for that (we will lern this feature in [Regular expressions](info:regular-expressions)).
|
||||
- The next page URL may look like `https://api.github.com/repositories/93253246/commits?page=2`. It's generated by GitHub itself.
|
||||
4. Then we yield the received commits one by one, and when they finish, the next `while(url)` iteration will trigger, making one more request.
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ Compare to regular script below:
|
|||
|
||||
<script>
|
||||
*!*
|
||||
alert(typeof button); // Error: button is undefined, the script can't see elements below
|
||||
alert(typeof button); // button is undefined, the script can't see elements below
|
||||
*/!*
|
||||
// regular scripts run immediately, before the rest of the page is processed
|
||||
</script>
|
||||
|
|
|
@ -321,7 +321,7 @@ export {default as User} from './user.js'; // re-export default
|
|||
|
||||
Why would that be needed? Let's see a practical use case.
|
||||
|
||||
Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages), and many modules are just "helpers", for internal use in other package modules.
|
||||
Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules.
|
||||
|
||||
The file structure could be like this:
|
||||
```
|
||||
|
@ -378,7 +378,7 @@ export {default as User} from './user.js';
|
|||
|
||||
The default export needs separate handling when re-exporting.
|
||||
|
||||
Let's say we have `user.js`, and we'd like to re-export class `User` from it:
|
||||
Let's say we have `user.js` with the `export default class User` and would like to re-export it:
|
||||
|
||||
```js
|
||||
// 📁 user.js
|
||||
|
@ -387,7 +387,9 @@ export default class User {
|
|||
}
|
||||
```
|
||||
|
||||
1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error!
|
||||
We can come across two problems with it:
|
||||
|
||||
1. `export User from './user.js'` won't work. That would lead to a syntax error.
|
||||
|
||||
To re-export the default export, we have to write `export {default as User}`, as in the example above.
|
||||
|
||||
|
@ -399,7 +401,7 @@ export default class User {
|
|||
export {default} from './user.js'; // to re-export the default export
|
||||
```
|
||||
|
||||
Such oddities of re-exporting the default export are one of the reasons why some developers don't like them.
|
||||
Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones.
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
@ -19,5 +19,5 @@ function wrap(target) {
|
|||
user = wrap(user);
|
||||
|
||||
alert(user.name); // John
|
||||
alert(user.age); // ReferenceError: Property doesn't exist "age"
|
||||
alert(user.age); // ReferenceError: Property doesn't exist: "age"
|
||||
```
|
||||
|
|
|
@ -27,6 +27,6 @@ user = wrap(user);
|
|||
|
||||
alert(user.name); // John
|
||||
*!*
|
||||
alert(user.age); // ReferenceError: Property doesn't exist "age"
|
||||
alert(user.age); // ReferenceError: Property doesn't exist: "age"
|
||||
*/!*
|
||||
```
|
||||
|
|
|
@ -39,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`.
|
|||
|
||||
As we can see, without any traps, `proxy` is a transparent wrapper around `target`.
|
||||
|
||||

|
||||

|
||||
|
||||
`Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`.
|
||||
|
||||
|
@ -335,7 +335,7 @@ let user = {
|
|||
_password: "secret"
|
||||
};
|
||||
|
||||
alert(user._password); // secret
|
||||
alert(user._password); // secret
|
||||
```
|
||||
|
||||
Let's use proxies to prevent any access to properties starting with `_`.
|
||||
|
@ -376,7 +376,7 @@ user = new Proxy(user, {
|
|||
},
|
||||
*!*
|
||||
deleteProperty(target, prop) { // to intercept property deletion
|
||||
*/!*
|
||||
*/!*
|
||||
if (prop.startsWith('_')) {
|
||||
throw new Error("Access denied");
|
||||
} else {
|
||||
|
@ -437,7 +437,7 @@ user = {
|
|||
```
|
||||
|
||||
|
||||
A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
|
||||
A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
|
||||
|
||||
So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Here's the explanations.
|
|||
|
||||
2. The same, parentheses do not change the order of operations here, the dot is first anyway.
|
||||
|
||||
3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
|
||||
3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines:
|
||||
|
||||
```js no-beautify
|
||||
f = obj.go; // calculate the expression
|
||||
|
@ -14,7 +14,7 @@ Here's the explanations.
|
|||
|
||||
Here `f()` is executed as a function, without `this`.
|
||||
|
||||
4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
|
||||
4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression.
|
||||
|
||||
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ Reference type is a special "intermediary" internal type, with the purpose to pa
|
|||
|
||||
Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`.
|
||||
|
||||
So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
|
||||
So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue