From 34e9cdca3642882bd36c6733433a503a40c6da74 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Mon, 29 Jul 2019 00:12:34 +0300 Subject: [PATCH] minor --- 1-js/04-object-basics/01-object/article.md | 4 +- 1-js/04-object-basics/03-symbol/article.md | 19 +++++---- .../04-object-methods/article.md | 3 +- .../05-object-toprimitive/article.md | 39 +++++++------------ .../06-constructor-new/3-accumulator/task.md | 2 + .../06-constructor-new/article.md | 2 + .../1-string-new-property/solution.md | 4 +- .../01-primitives-methods/article.md | 8 ++-- 1-js/05-data-types/02-number/article.md | 4 +- 6-data-storage/01-cookie/article.md | 32 +++++++-------- 10 files changed, 55 insertions(+), 62 deletions(-) diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 27a71428..2147fc19 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -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: diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md index bd02a97e..7fc81d1e 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/03-symbol/article.md @@ -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. diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index fefcb908..3d83a224 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -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: diff --git a/1-js/04-object-basics/05-object-toprimitive/article.md b/1-js/04-object-basics/05-object-toprimitive/article.md index 5018637f..50efbb00 100644 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ b/1-js/04-object-basics/05-object-toprimitive/article.md @@ -15,9 +15,7 @@ In the chapter 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) diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md index 3362b5b4..c2c44881 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md @@ -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 ``` diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 4a7922a7..66adf400 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -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. diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md index 13b71b2f..fd22a465 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md @@ -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.** diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index bc94ef99..b358b27e 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -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 and . ````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. diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 57162d75..634baf15 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -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 diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 048831cc..66e5cbe8 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -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 `
` 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 `` 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 `