This commit is contained in:
Ilya Kantor 2019-07-29 00:12:34 +03:00
parent 3ba28aa104
commit 34e9cdca36
10 changed files with 55 additions and 62 deletions

View file

@ -322,7 +322,7 @@ alert( *!*key*/!* in user ); // true, takes the name from key and checks for suc
```
````smart header="Using \"in\" for properties that store `undefined`"
Usually, the strict comparison `"=== undefined"` check works fine. But there's a special case when it fails, but `"in"` works correctly.
Usually, the strict comparison `"=== undefined"` check the property existance just fine. But there's a special case when it fails, but `"in"` works correctly.
It's when an object property exists, but stores `undefined`:
@ -569,7 +569,7 @@ user.age = 25; // (*)
alert(user.age); // 25
```
It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes the value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`.
It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes only value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`.
The `const` would give an error if we try to set `user` to something else, for instance:

View file

@ -192,9 +192,7 @@ alert( obj[0] ); // test (same property)
## Global symbols
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.
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.
@ -230,22 +228,29 @@ For global symbols, not only `Symbol.for(key)` returns a symbol by name, but the
For instance:
```js run
// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name from symbol
// get name by symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
```
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`.
That said, any symbols have `description` property.
For instance:
```js run
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol
alert( Symbol.keyFor(globalSymbol) ); // name, global symbol
alert( Symbol.keyFor(localSymbol) ); // undefined, not global
alert( localSymbol.description ); // name
```
## System symbols
@ -281,4 +286,4 @@ Symbols have two main use cases:
2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on.
Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he's doing.
Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods.

View file

@ -111,6 +111,7 @@ let user = {
sayHi() {
*!*
// "this" is the "current object"
alert(this.name);
*/!*
}
@ -176,7 +177,7 @@ function sayHi() {
}
```
The value of `this` is evaluated during the run-time, depending on the context. And it can be anything.
The value of `this` is evaluated during the run-time, depending on the context.
For instance, here the same function is assigned to two different objects and has different "this" in the calls:

View file

@ -15,9 +15,7 @@ In the chapter <info:type-conversions> we've seen the rules for numeric, string
We can fine-tune string and numeric conversion, using special object methods.
The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type.
There are three variants:
There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
`"string"`
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
@ -66,7 +64,7 @@ Please note -- there are only three hints. It's that simple. There is no "boolea
**To do the conversion, JavaScript tries to find and call three object methods:**
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
2. Otherwise if hint is `"string"`
- try `obj.toString()` and `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
@ -78,9 +76,9 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri
```js
obj[Symbol.toPrimitive] = function(hint) {
// return a primitive value
// must return a primitive value
// hint = one of "string", "number", "default"
}
};
```
For instance, here `user` object implements it:
@ -138,6 +136,8 @@ alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
```
As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`.
Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this:
```js run
@ -171,25 +171,24 @@ In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there wil
## Further operations
An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
An operation that initiated the conversion gets the primitive, and then continues to work with it, applying further conversions if necessary.
For instance:
- Mathematical operations (except binary plus) perform `ToNumber` conversion:
- Mathematical operations, except binary plus, convert the primitive to a number:
```js run
let obj = {
toString() { // toString handles all conversions in the absence of other methods
// toString handles all conversions in the absence of other methods
toString() {
return "2";
}
};
alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2
alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
```
- Binary plus checks the primitive -- if it's a string, then it does concatenation, otherwise it performs `ToNumber` and works with numbers.
String example:
- Binary plus will concatenate strings in the same situation:
```js run
let obj = {
toString() {
@ -200,24 +199,12 @@ For instance:
alert(obj + 2); // 22 (ToPrimitive returned string => concatenation)
```
Number example:
```js run
let obj = {
toString() {
return true;
}
};
alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)
```
## Summary
The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value.
There are 3 types (hints) of it:
- `"string"` (for `alert` and other string conversions)
- `"string"` (for `alert` and other operations that need a string)
- `"number"` (for maths)
- `"default"` (few operators)

View file

@ -17,8 +17,10 @@ Here's the demo of the code:
```js
let accumulator = new Accumulator(1); // initial value 1
accumulator.read(); // adds the user-entered value
accumulator.read(); // adds the user-entered value
alert(accumulator.value); // shows the sum of these values
```

View file

@ -215,6 +215,8 @@ john = {
*/
```
To create complex objects, there's a more advanced syntax, [classes](info:classes), that we'll cover later.
## Summary
- Constructor functions or, briefly, constructors, are regular functions, but there's a common agreement to name them with capital letter first.

View file

@ -17,9 +17,7 @@ Why? Let's replay what's happening at line `(*)`:
1. When a property of `str` is accessed, a "wrapper object" is created.
2. In strict mode, writing into it is an error.
3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears.
So, without strict mode, in the last line `str` has no trace of the property.
3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears, so in the last line `str` has no trace of the property.
**This example clearly shows that primitives are not objects.**

View file

@ -1,8 +1,6 @@
# Methods of primitives
JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects.
They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer).
JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer).
Let's look at the key distinctions between primitives and objects.
@ -35,7 +33,7 @@ Many built-in objects already exist, such as those that work with dates, errors,
But, these features come with a cost!
Objects are "heavier" than primitives. They require additional resources to support the internal machinery. But as properties and methods are very useful in programming, JavaScript engines try to optimize them to reduce the additional burden.
Objects are "heavier" than primitives. They require additional resources to support the internal machinery.
## A primitive as an object
@ -84,7 +82,7 @@ We'll see more specific methods in chapters <info:number> and <info:string>.
````warn header="Constructors `String/Number/Boolean` are for internal use only"
Some languages like Java allow us to create "wrapper objects" for primitives explicitly using a syntax like `new Number(1)` or `new Boolean(false)`.
Some languages like Java allow us to explicitly create "wrapper objects" for primitives using a syntax like `new Number(1)` or `new Boolean(false)`.
In JavaScript, that's also possible for historical reasons, but highly **unrecommended**. Things will go crazy in several places.

View file

@ -2,7 +2,7 @@
All numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers".
Let's recap and expand upon what we currently know about them.
Let's expand upon what we currently know about them.
## More ways to write a number
@ -213,7 +213,7 @@ So, division by powers `10` is guaranteed to work well in the decimal system, bu
There's just no way to store *exactly 0.1* or *exactly 0.2* using the binary system, just like there is no way to store one-third as a decimal fraction.
The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", so the number shows up as `0.3`. But beware, the loss still exists.
The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", but it exists.
We can see this in action:
```js run

View file

@ -55,7 +55,7 @@ If you run it, then probably you'll see multiple cookies. That's because `docume
Technically, name and value can have any characters, to keep the valid formatting they should be escaped using a built-in `encodeURIComponent` function:
```js run
// special values, need encoding
// special characters (spaces), need encoding
let name = "my name";
let value = "John Smith"
@ -192,17 +192,17 @@ To understand how it works and when it's useful, let's take a look at XSRF attac
Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations.
Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that automatically submits a form `<form action="https://bank.com/pay">` to `bank.com` with input fields that initiate a transaction to the hacker's account.
Now, while browsing the web in another window, you occasionally come to another site `evil.com`. That site has JavaScript code that submits a form `<form action="https://bank.com/pay">` to `bank.com` with fields that initiate a transaction to the hacker's account.
The form is submitted from `evil.com` directly to the bank site, and your cookie is also sent, just because it's sent every time you visit `bank.com`. So the bank recognizes you and actually performs the payment.
The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and actually performs the payment.
![](cookie-xsrf.svg)
That's called a cross-site request forgery (or XSRF) attack.
That's called a "Cross-Site Request Forgery" (in short, XSRF) attack.
Real banks are protected from it of course. All forms generated by `bank.com` have a special field, so called "xsrf protection token", that an evil page can't neither generate, nor somehow extract from a remote page (it can submit a form there, but can't get the data back).
Real banks are protected from it of course. All forms generated by `bank.com` have a special field, so called "XSRF protection token", that an evil page can't generate or extract from a remote page (it can submit a form there, but can't get the data back). And the site `bank.com` checks for such token in every form it receives.
But that takes time to implement: we need to ensure that every form has the token field, and we must also check all requests.
But such protection takes time to implement: we need to ensure that every form has the token field, and we must also check all requests.
### Enter cookie samesite option
@ -212,19 +212,19 @@ It has two possible values:
- **`samesite=strict` (same as `samesite` without value)**
A cookie with `samesite=strict` is never sent if the user comes from outside the site.
A cookie with `samesite=strict` is never sent if the user comes from outside the same site.
In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent.
If authentication cookies have `samesite` option, then XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment.
The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie.
The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie, e.g. a form submission from another page at `bank.com`.
Although, there's a small inconvenience.
When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case.
We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website.
We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website, for the second cookie to be sent.
- **`samesite=lax`**
@ -239,11 +239,11 @@ A `samesite=lax` cookie is sent if both of these conditions are true:
2. The operation performs top-level navigation (changes URL in the browser address bar).
That's usually true, but if the navigation is performed in an `<iframe>`, then it's not top-level. Also, AJAX requests do not perform any navigation, hence they don't fit.
That's usually true, but if the navigation is performed in an `<iframe>`, then it's not top-level. Also, JavaScript methods for network requests do not perform any navigation, hence they don't fit.
So, what `samesite=lax` does is basically allows a most common "go to URL" operation to have cookies. E.g. opening a website link from notes satisfies these conditions.
But anything more complicated, like AJAX request from another site or a form submittion loses cookies.
But anything more complicated, like a network request from another site or a form submittion loses cookies.
If that's fine for you, then adding `samesite=lax` will probably not break the user experience and add protection.
@ -265,7 +265,7 @@ This option forbids any JavaScript access to the cookie. We can't see such cooki
That's used as a precaution measure, to protect from certain attacks when a hacker injects his own JavaScript code into a page and waits for a user to visit that page. That shouldn't be possible at all, a hacker should not be able to inject their code into our site, but there may be bugs that let hackers do it.
Normally, if such thing happens, and a user visits a web-page with hacker's code, then that code executes and gains access to `document.cookie` with user cookies containing authentication information. That's bad.
Normally, if such thing happens, and a user visits a web-page with hacker's JavaScript code, then that code executes and gains access to `document.cookie` with user cookies containing authentication information. That's bad.
But if a cookie is `httpOnly`, then `document.cookie` doesn't see it, so it is protected.
@ -352,7 +352,7 @@ Together: [cookie.js](cookie.js).
## Appendix: Third-party cookies
A cookie is called "third-party" if it's placed by domain other than the user is visiting.
A cookie is called "third-party" if it's placed by domain other than the page user is visiting.
For instance:
1. A page at `site.com` loads a banner from another site: `<img src="https://ads.com/banner.png">`.
@ -381,7 +381,7 @@ Also, some modern browsers employ special policies for such cookies:
```smart
If we load a script from a third-party domain, like `<script src="https://google-analytics.com/analytics.js">`, and that script uses `document.cookie` to set a cookie, then such cookie is not third-party.
If a script sets a cookie, then no matter where the script came from -- it belongs to the domain of the current webpage.
If a script sets a cookie, then no matter where the script came from -- the cookie belongs to the domain of the current webpage.
```
## Appendix: GDPR
@ -390,7 +390,7 @@ This topic is not related to JavaScript at all, just something to keep in mind w
There's a legislation in Europe called GDPR, that enforces a set of rules for websites to respect users' privacy. And one of such rules is to require an explicit permission for tracking cookies from a user.
Please note, that's only about tracking/identifying cookies.
Please note, that's only about tracking/identifying/authorizing cookies.
So, if we set a cookie that just saves some information, but neither tracks nor identifies the user, then we are free to do it.
@ -400,7 +400,7 @@ Websites generally have two variants of following GDPR. You must have seen them
1. If a website wants to set tracking cookies only for authenticated users.
To do so, the registration form should have a checkbox like "accept the privacy policy", the user must check it, and then the website is free to set auth cookies.
To do so, the registration form should have a checkbox like "accept the privacy policy" (that describes how cookies are used), the user must check it, and then the website is free to set auth cookies.
2. If a website wants to set tracking cookies for everyone.