fixes
This commit is contained in:
parent
f6e2c899c2
commit
5498450646
4 changed files with 124 additions and 22 deletions
|
@ -58,7 +58,24 @@ alert( visitsCountMap.get(john) ); // 123
|
||||||
|
|
||||||
Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but it would be difficult to replace the `Map` with a regular `Object` in the example above.
|
Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but it would be difficult to replace the `Map` with a regular `Object` in the example above.
|
||||||
|
|
||||||
In the old times, before `Map` existed, people added unique identifiers to objects for that:
|
Let's try:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let john = { name: "John" };
|
||||||
|
|
||||||
|
let visitsCountObj = {}; // try to use an object
|
||||||
|
|
||||||
|
visitsCountObj[john] = 123; // try to use john object as the key
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// That's what got written!
|
||||||
|
alert( visitsCountObj["[object Object]"] ); // 123
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
As `john` is an object, it got converted to the key string `"[object Object]"`. All objects without a special conversion handling are converted to such string, so they'll all mess up.
|
||||||
|
|
||||||
|
In the old times, before `Map` existed, people used to add unique identifiers to objects for that:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// we add the id field
|
// we add the id field
|
||||||
|
|
|
@ -63,8 +63,93 @@ for (let value of Object.values(user)) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Object.keys/values/entries ignore symbolic properties
|
```warn header="Object.keys/values/entries ignore symbolic properties"
|
||||||
|
|
||||||
Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys.
|
Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys.
|
||||||
|
|
||||||
Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns *all* keys.
|
Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Object.fromEntries to transform objects
|
||||||
|
|
||||||
|
Sometimes we need to perform a transformation of an object to `Map` and back.
|
||||||
|
|
||||||
|
We already have `new Map(Object.entries(obj))` to make a `Map` from `obj`.
|
||||||
|
|
||||||
|
The syntax of `Object.fromEntries` does the reverse. Given an array of `[key, value]` pairs, it creates an object:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let prices = Object.fromEntries([
|
||||||
|
['banana', 1],
|
||||||
|
['orange', 2],
|
||||||
|
['meat', 4]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// now prices = { banana: 1, orange: 2, meat: 4 }
|
||||||
|
|
||||||
|
alert(prices.orange); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's see practical applications.
|
||||||
|
|
||||||
|
For example, we'd like to create a new object with double prices from the existing one.
|
||||||
|
|
||||||
|
For arrays, we have `.map` method that allows to transform an array, but nothing like that for objects.
|
||||||
|
|
||||||
|
So we can use a loop:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let prices = {
|
||||||
|
banana: 1,
|
||||||
|
orange: 2,
|
||||||
|
meat: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
let doublePrices = {};
|
||||||
|
for(let [product, price] of Object.entries(prices)) {
|
||||||
|
doublePrices[product] = price * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(doublePrices.meat); // 8
|
||||||
|
```
|
||||||
|
|
||||||
|
...Or we can represent the object as an `Array` using `Object.entries`, then perform the operations with `map` (and potentially other array methods), and then go back using `Object.fromEntries`.
|
||||||
|
|
||||||
|
Let's do it for our object:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let prices = {
|
||||||
|
banana: 1,
|
||||||
|
orange: 2,
|
||||||
|
meat: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let doublePrices = Object.fromEntries(
|
||||||
|
// convert to array, map, and then fromEntries gives back the object
|
||||||
|
Object.entries(prices).map(([key, value]) => [key, value * 2])
|
||||||
|
);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert(doublePrices.meat); // 8
|
||||||
|
```
|
||||||
|
|
||||||
|
It may look difficult from the first sight, but becomes easy to understand after you use it once or twice.
|
||||||
|
|
||||||
|
We also can use `fromEntries` to get an object from `Map`.
|
||||||
|
|
||||||
|
E.g. we have a `Map` of prices, but we need to pass it to a 3rd-party code that expects an object.
|
||||||
|
|
||||||
|
Here we go:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let map = new Map();
|
||||||
|
map.set('banana', 1);
|
||||||
|
map.set('orange', 2);
|
||||||
|
map.set('meat', 4);
|
||||||
|
|
||||||
|
let obj = Object.fromEntries(map);
|
||||||
|
|
||||||
|
// now obj = { banana: 1, orange: 2, meat: 4 }
|
||||||
|
|
||||||
|
alert(obj.orange); // 2
|
||||||
|
```
|
||||||
|
|
|
@ -99,17 +99,17 @@ new TypedArray();
|
||||||
*!*
|
*!*
|
||||||
let arr = new Uint8Array([0, 1, 2, 3]);
|
let arr = new Uint8Array([0, 1, 2, 3]);
|
||||||
*/!*
|
*/!*
|
||||||
alert( arr.length ); // 4
|
alert( arr.length ); // 4, created binary array of the same length
|
||||||
alert( arr[1] ); // 1
|
alert( arr[1] ); // 1, filled with 4 bytes (unsigned 8-bit integers) with given values
|
||||||
```
|
```
|
||||||
3. If another `TypedArray` is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process.
|
3. If another `TypedArray` is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process, if needed.
|
||||||
```js run
|
```js run
|
||||||
let arr16 = new Uint16Array([1, 1000]);
|
let arr16 = new Uint16Array([1, 1000]);
|
||||||
*!*
|
*!*
|
||||||
let arr8 = new Uint8Array(arr16);
|
let arr8 = new Uint8Array(arr16);
|
||||||
*/!*
|
*/!*
|
||||||
alert( arr8[0] ); // 1
|
alert( arr8[0] ); // 1
|
||||||
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
|
alert( arr8[1] ); // 232, tried to copy 1000, but can't fit 1000 into 8 bits (explanations below)
|
||||||
```
|
```
|
||||||
|
|
||||||
4. For a numeric argument `length` -- creates the typed array to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
|
4. For a numeric argument `length` -- creates the typed array to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
|
||||||
|
@ -224,6 +224,7 @@ new DataView(buffer, [byteOffset], [byteLength])
|
||||||
For instance, here we extract numbers in different formats from the same buffer:
|
For instance, here we extract numbers in different formats from the same buffer:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
// binary array of 4 bytes, all have the maximal value 255
|
||||||
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
|
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
|
||||||
|
|
||||||
let dataView = new DataView(buffer);
|
let dataView = new DataView(buffer);
|
||||||
|
@ -231,13 +232,13 @@ let dataView = new DataView(buffer);
|
||||||
// get 8-bit number at offset 0
|
// get 8-bit number at offset 0
|
||||||
alert( dataView.getUint8(0) ); // 255
|
alert( dataView.getUint8(0) ); // 255
|
||||||
|
|
||||||
// now get 16-bit number at offset 0, that's 2 bytes, both with max value
|
// now get 16-bit number at offset 0, it consists of 2 bytes, together iterpreted as 65535
|
||||||
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
|
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
|
||||||
|
|
||||||
// get 32-bit number at offset 0
|
// get 32-bit number at offset 0
|
||||||
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
|
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
|
||||||
|
|
||||||
dataView.setUint32(0, 0); // set 4-byte number to zero
|
dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0
|
||||||
```
|
```
|
||||||
|
|
||||||
`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
|
`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
|
||||||
|
@ -257,12 +258,11 @@ To do almost any operation on `ArrayBuffer`, we need a view.
|
||||||
|
|
||||||
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
|
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
|
||||||
|
|
||||||
There are also two additional terms:
|
There are also two additional terms, that are used in descriptions of methods that operate on binary data:
|
||||||
- `ArrayBufferView` is an umbrella term for all these kinds of views.
|
- `ArrayBufferView` is an umbrella term for all these kinds of views.
|
||||||
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
|
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
|
||||||
|
|
||||||
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common terms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
|
We'll see these terms in the next chapters. `BufferSource` is one of the most common terms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
|
||||||
|
|
||||||
|
|
||||||
Here's a cheatsheet:
|
Here's a cheatsheet:
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
The idea behind shadow tree is to encapsulate internal implementation details of a component.
|
The idea behind shadow tree is to encapsulate internal implementation details of a component.
|
||||||
|
|
||||||
Let's say, a click event happens inside a shadow DOM of `<user-card>` component. But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library or so.
|
Let's say, a click event happens inside a shadow DOM of `<user-card>` component. But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library.
|
||||||
|
|
||||||
So, to keep things simple, the browser *retargets* the event.
|
So, to keep the details encapsulated, the browser *retargets* the event.
|
||||||
|
|
||||||
**Events that happen in shadow DOM have the host element as the target, when caught outside of the component.**
|
**Events that happen in shadow DOM have the host element as the target, when caught outside of the component.**
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ Event retargeting is a great thing to have, because the outer document doesn't h
|
||||||
|
|
||||||
**Retargeting does not occur if the event occurs on a slotted element, that physically lives in the light DOM.**
|
**Retargeting does not occur if the event occurs on a slotted element, that physically lives in the light DOM.**
|
||||||
|
|
||||||
For example, if a user clicks on `<span slot="username">` in the example below, the event target is exactly this element, for both shadow and light handlers:
|
For example, if a user clicks on `<span slot="username">` in the example below, the event target is exactly this `span` element, for both shadow and light handlers:
|
||||||
|
|
||||||
```html run autorun="no-epub" untrusted height=60
|
```html run autorun="no-epub" untrusted height=60
|
||||||
<user-card id="userCard">
|
<user-card id="userCard">
|
||||||
|
@ -75,7 +75,7 @@ For purposes of event bubbling, flattened DOM is used.
|
||||||
|
|
||||||
So, if we have a slotted element, and an event occurs somewhere inside it, then it bubbles up to the `<slot>` and upwards.
|
So, if we have a slotted element, and an event occurs somewhere inside it, then it bubbles up to the `<slot>` and upwards.
|
||||||
|
|
||||||
The full path to the original event target, with all the shadow root elements, can be obtained using `event.composedPath()`. As we can see from the name of the method, that path is taken after the composition.
|
The full path to the original event target, with all the shadow elements, can be obtained using `event.composedPath()`. As we can see from the name of the method, that path is taken after the composition.
|
||||||
|
|
||||||
In the example above, the flattened DOM is:
|
In the example above, the flattened DOM is:
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ All touch events and pointer events also have `composed: true`.
|
||||||
|
|
||||||
There are some events that have `composed: false` though:
|
There are some events that have `composed: false` though:
|
||||||
|
|
||||||
- `mouseenter`, `mouseleave` (they also do not bubble),
|
- `mouseenter`, `mouseleave` (they do not bubble at all),
|
||||||
- `load`, `unload`, `abort`, `error`,
|
- `load`, `unload`, `abort`, `error`,
|
||||||
- `select`,
|
- `select`,
|
||||||
- `slotchange`.
|
- `slotchange`.
|
||||||
|
@ -189,4 +189,4 @@ These events can be caught only on elements within the same DOM.
|
||||||
|
|
||||||
If we dispatch a `CustomEvent`, then we should explicitly set `composed: true`.
|
If we dispatch a `CustomEvent`, then we should explicitly set `composed: true`.
|
||||||
|
|
||||||
Please note that in case of nested components, composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host. Then it's out of the shadow DOM already.
|
Please note that in case of nested components, one shadow DOM may be nested into another. In that case composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host and set `composed: false`. Then it's out of the component shadow DOM, but won't bubble up to higher-level DOM.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue