up
|
@ -226,7 +226,7 @@ As we see from the code, the assignment to a primitive `5` is ignored. If we wan
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
||||||
## Property name shorthand
|
## Property value shorthand
|
||||||
|
|
||||||
In real code we often use existing variables as values for property names.
|
In real code we often use existing variables as values for property names.
|
||||||
|
|
||||||
|
@ -270,7 +270,6 @@ let user = {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Existance check
|
## Existance check
|
||||||
|
|
||||||
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:
|
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:
|
||||||
|
|
|
@ -219,7 +219,7 @@ Please note that usually a call of a function using `this` without an object is
|
||||||
```smart header="The consequences of unbound `this`"
|
```smart header="The consequences of unbound `this`"
|
||||||
If you come from another programming languages, then you are probably used to an idea of a "bound `this`", where methods defined in an object always have `this` referencing that object.
|
If you come from another programming languages, then you are probably used to an idea of a "bound `this`", where methods defined in an object always have `this` referencing that object.
|
||||||
|
|
||||||
The idea of unbound, run-time evaluated `this` has both pluses and minuses. From one side, a function can be reused for different objects. From the other side, it's possible to occasionally loose `this` by making an improper call.
|
The idea of unbound, run-time evaluated `this` has both pluses and minuses. From one side, a function can be reused for different objects. From the other side, greater flexibility opens a place for mistakes.
|
||||||
|
|
||||||
Here we are not to judge whether this language design decision is good or bad. We will understand how to work with it, how to get benefits and evade problems.
|
Here we are not to judge whether this language design decision is good or bad. We will understand how to work with it, how to get benefits and evade problems.
|
||||||
```
|
```
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives.
|
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives.
|
||||||
|
|
||||||
But we left a gap for objects. Now let's fill it.
|
But we left a gap for objects. Now let's close it. And, in the process, we'll see some built-in methods and the example of a built-in symbol.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Methods of primitives
|
# Methods of primitives
|
||||||
|
|
||||||
JavaScript allows to work with primitives (strings, numbers etc) as if they were objects. They also have methods and such. Of course, primitives are not objects (and here we plan to make it even more clear), but can be used like them.
|
JavaScript allows to work with primitives (strings, numbers etc) as if they were objects.
|
||||||
|
|
||||||
|
They also provide methods to call and such. We are going to study them soon, but first let's see how it works, because, of course, primitives are not objects (and here we plan to make it even more clear).
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
|
@ -92,4 +94,3 @@ alert(null.test); // error
|
||||||
|
|
||||||
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters.
|
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters.
|
||||||
- Formally, these methods work via temporary objects, but JavaScript engines are very well tuned to optimize that internally, so they are not expensive to call.
|
- Formally, these methods work via temporary objects, but JavaScript engines are very well tuned to optimize that internally, so they are not expensive to call.
|
||||||
|
|
||||||
|
|
|
@ -669,9 +669,8 @@ If you want to learn more about normalization rules and variants -- they are des
|
||||||
|
|
||||||
There are several other helpful methods in strings:
|
There are several other helpful methods in strings:
|
||||||
|
|
||||||
- [str.trim()]` -- removes ("trims") spaces from the beginning and end of the string.
|
- `str.trim()` -- removes ("trims") spaces from the beginning and end of the string.
|
||||||
- [str.repeat(n)]` -- repeats the string `n` times.
|
- `str.repeat(n)` -- repeats the string `n` times.
|
||||||
- ...and others, see the [manual](mdn:js/String) for details.
|
- ...and others, see the [manual](mdn:js/String) for details.
|
||||||
|
|
||||||
Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.
|
Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
@ -320,9 +320,9 @@ But that's actually a bad idea. There are potential problems with it:
|
||||||
|
|
||||||
1. The loop `for..in` iterates over *all properties*, not only the numeric ones.
|
1. The loop `for..in` iterates over *all properties*, not only the numeric ones.
|
||||||
|
|
||||||
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they have *other non-numeric properties and methods*, which we usually don't need in the loop. The `for..in` will list them. If we need to work with array-like objects, then these "extra" properties can become a problem.
|
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem.
|
||||||
|
|
||||||
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup maybe important in few places bottlenecks or just irrelevant. But still we should be aware of the difference.
|
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may matter only in bottlenecks or just irrelevant. But still we should be aware of the difference.
|
||||||
|
|
||||||
Generally, we shouldn't use `for..in` for arrays.
|
Generally, we shouldn't use `for..in` for arrays.
|
||||||
|
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
@ -1,15 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Сколько секунд уже прошло сегодня?
|
|
||||||
|
|
||||||
Напишите функцию `getSecondsToday()` которая возвращает, сколько секунд прошло с начала сегодняшнего дня.
|
|
||||||
|
|
||||||
Например, если сейчас `10:00` и не было перехода на зимнее/летнее время, то:
|
|
||||||
|
|
||||||
```js
|
|
||||||
getSecondsToday() == 36000 // (3600 * 10)
|
|
||||||
```
|
|
||||||
|
|
||||||
Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.
|
|
|
@ -1,34 +0,0 @@
|
||||||
function formatDate(date) {
|
|
||||||
var diff = new Date() - date; // number of ms till now
|
|
||||||
|
|
||||||
if (diff < 1000) { // less than a second
|
|
||||||
return 'right now';
|
|
||||||
}
|
|
||||||
|
|
||||||
var sec = Math.floor(diff / 1000); // get seconds
|
|
||||||
|
|
||||||
if (sec < 60) {
|
|
||||||
return sec + ' sec. ago';
|
|
||||||
}
|
|
||||||
|
|
||||||
var min = Math.floor(diff / 60000); // get minutes
|
|
||||||
if (min < 60) {
|
|
||||||
return min + ' min. ago';
|
|
||||||
}
|
|
||||||
|
|
||||||
// format the date, take into account that months start from zero
|
|
||||||
var d = date;
|
|
||||||
d = [
|
|
||||||
'0' + d.getDate(),
|
|
||||||
'0' + (d.getMonth() + 1),
|
|
||||||
'' + d.getFullYear(),
|
|
||||||
'0' + d.getHours(),
|
|
||||||
'0' + d.getMinutes()
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < d.length; i++) {
|
|
||||||
d[i] = d[i].slice(-2); // remove extra zeroes
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
Для того, чтобы узнать время от `date` до текущего момента - используем вычитание дат.
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function formatDate(date) {
|
|
||||||
var diff = new Date() - date; // разница в миллисекундах
|
|
||||||
|
|
||||||
if (diff < 1000) { // прошло менее 1 секунды
|
|
||||||
return 'только что';
|
|
||||||
}
|
|
||||||
|
|
||||||
var sec = Math.floor(diff / 1000); // округлить diff до секунд
|
|
||||||
|
|
||||||
if (sec < 60) {
|
|
||||||
return sec + ' сек. назад';
|
|
||||||
}
|
|
||||||
|
|
||||||
var min = Math.floor(diff / 60000); // округлить diff до минут
|
|
||||||
if (min < 60) {
|
|
||||||
return min + ' мин. назад';
|
|
||||||
}
|
|
||||||
|
|
||||||
// форматировать дату, с учетом того, что месяцы начинаются с 0
|
|
||||||
var d = date;
|
|
||||||
d = [
|
|
||||||
'0' + d.getDate(),
|
|
||||||
'0' + (d.getMonth() + 1),
|
|
||||||
'' + d.getFullYear(),
|
|
||||||
'0' + d.getHours(),
|
|
||||||
'0' + d.getMinutes()
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < d.length; i++) {
|
|
||||||
d[i] = d[i].slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
alert( formatDate(new Date(new Date - 1)) ); // только что
|
|
||||||
|
|
||||||
alert( formatDate(new Date(new Date - 30 * 1000)) ); // 30 сек. назад
|
|
||||||
|
|
||||||
alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // 5 мин. назад
|
|
||||||
|
|
||||||
alert( formatDate(new Date(new Date - 86400 * 1000)) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
|
|
||||||
```
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ Arrays provide a lot of methods. To make things easier, in this chapter they are
|
||||||
|
|
||||||
We already know methods that add and remove items from the beginning or the end:
|
We already know methods that add and remove items from the beginning or the end:
|
||||||
|
|
||||||
- `arr.push(...items)`
|
- `arr.push(...items)` -- adds items to the end,
|
||||||
- `arr.pop()`
|
- `arr.pop()` -- extracts an item from the end,
|
||||||
- `arr.shift(...items)`
|
- `arr.shift(...items)` -- adds items to the beginning,
|
||||||
- `arr.unshift()`
|
- `arr.unshift()` -- extracts an item from the beginning.
|
||||||
|
|
||||||
Here are few others.
|
Here are few others.
|
||||||
|
|
||||||
|
@ -614,6 +614,9 @@ alert(Array.isArray({})); // false
|
||||||
alert(Array.isArray([])); // true
|
alert(Array.isArray([])); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```smart header="`Array.isArray` vs other type-checks"
|
||||||
|
You remembeare other ways to check for
|
||||||
|
|
||||||
## Methods: "thisArg"
|
## Methods: "thisArg"
|
||||||
|
|
||||||
Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`.
|
Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`.
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -1,11 +1,11 @@
|
||||||
|
|
||||||
# Iterables
|
# Iterables
|
||||||
|
|
||||||
*Iterable* objects is a general concept that allows to make any object useable in a `for..of` loop.
|
*Iterable* objects is a generalization of arrays. That's a concept that allows to make any object useable in a `for..of` loop.
|
||||||
|
|
||||||
Many built-ins are partial cases of this concept. For instance, arrays are iterable. But not only arrays. Strings are iterable too.
|
Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well.
|
||||||
|
|
||||||
Iterables come from the very core of Javascript and are widely used both in built-in methods and those provided by the environment.
|
Iterables are widely used by the core Javascript, as we'll see many operators and built-in methods rely on them.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
|
@ -47,13 +47,13 @@ range[Symbol.iterator] = function() {
|
||||||
|
|
||||||
// 2. ...it returns the iterator:
|
// 2. ...it returns the iterator:
|
||||||
return {
|
return {
|
||||||
current: this.from, // remember "from" and "to" of the range
|
current: this.from, // start at "range.from",
|
||||||
last: this.to, // in object properties
|
last: this.to, // end at "range.to"
|
||||||
|
|
||||||
// 3. next() is called on each iteration of the loop
|
// 3. next() is called on each iteration by for..of
|
||||||
next() {
|
next() {
|
||||||
if (this.current <= this.last) {
|
if (this.current <= this.last) {
|
||||||
// 4. iterator returns the value as an object {done:.., value :...}
|
// 4. it should return the value as an object {done:.., value :...}
|
||||||
return { done: false, value: this.current++ };
|
return { done: false, value: this.current++ };
|
||||||
} else {
|
} else {
|
||||||
return { done: true };
|
return { done: true };
|
||||||
|
@ -67,37 +67,51 @@ for (let num of range) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There is an important separation of concerns in this code.
|
There is an important separation of concerns in this code:
|
||||||
|
|
||||||
- The `range` itself does not have the `next()` method.
|
- The `range` itself does not have the `next()` method.
|
||||||
- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`.
|
- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and it handles the iteration.
|
||||||
- It keeps the iteration state in its `current` property. That's good, because the original object is not modified by iterations. Also multiple `for..of` loops over the same object can run simultaneously, because they create separate iterators.
|
|
||||||
|
|
||||||
The fact that the object itself does not do the iteration also adds flexibility, because `range[Symbol.iterator]` can create iterators the smart way, depending on other object properties or external conditions. It's a full-fledged function that may be more complex than just a `return {...}`.
|
So, the iterator is separate from the object.
|
||||||
|
|
||||||
Please note that the internal mechanics is not seen from outside. Here `for..of` calls `range[Symbol.iterator]()` and then the `next()` until `done: false`, but the external code doesn't see that. It only gets values.
|
Technically, we may merge them and use `range` itself as the iterator, to make the code simpler.
|
||||||
|
|
||||||
|
Like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let range = {
|
||||||
|
from: 1,
|
||||||
|
to: 5,
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
this.current = this.from;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (this.current <= this.to) {
|
||||||
|
return { done: false, value: this.current++ };
|
||||||
|
} else {
|
||||||
|
return { done: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let num of range) {
|
||||||
|
alert(num); // 1, then 2, 3, 4, 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now `range[Symbol.iterator]()` returns the `range` object itself, and it has the necessary `next()` method. Sometimes that's fine too. The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself.
|
||||||
|
|
||||||
```smart header="Infinite iterators"
|
```smart header="Infinite iterators"
|
||||||
Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful.
|
Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful.
|
||||||
|
|
||||||
There are no limitations on `next`, it can return more and more values, that's normal.
|
There are no limitations on `next`, it can return more and more values, that's normal.
|
||||||
|
|
||||||
Of course, the `for..of` loop over such an iterable would be endless, we'll need to stop if, for instance, using `break`.
|
Of course, the `for..of` loop over such an iterable would be endless. But we can always stop it using `break`.
|
||||||
```
|
```
|
||||||
|
|
||||||
````smart header="`Symbol.iterator` in a literal"
|
|
||||||
We could also write `Symbol.iterator` directly in the object literal, via computed properties syntax:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let range = {
|
|
||||||
from: 1,
|
|
||||||
to: 5,
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return {...};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
## String is iterable
|
## String is iterable
|
||||||
|
|
||||||
|
@ -120,8 +134,11 @@ for(let char of str) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
````smart header="Calling an iterator manually"
|
## Calling an iterator explicitly
|
||||||
Iterators can also be created explicitly, without `for..of`, with a direct call of `Symbol.iterator`. For built-in objects too.
|
|
||||||
|
Normally, internals of iterables are hidden from the external code. There's a `for..of` loop, that works, that's all it needs to know.
|
||||||
|
|
||||||
|
But to understand things a little bit more deeper let's see how to create an iterator explicitly. We'll do that same as `for..of`, but with direct calls.
|
||||||
|
|
||||||
For instance, this code gets a string iterator and calls it "manually":
|
For instance, this code gets a string iterator and calls it "manually":
|
||||||
|
|
||||||
|
@ -140,17 +157,16 @@ while(true) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That is a little bit more flexible than `for..of`, because we can split the iteration process: iterate a bit, then stop, do something else, and then continue later.
|
That is rarely needed, but gives us more control than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later.
|
||||||
````
|
|
||||||
|
|
||||||
## Iterables and array-likes [#array-like]
|
## Iterables and array-likes [#array-like]
|
||||||
|
|
||||||
There are two official terms that are similar, but actually very different. Please be careful to avoid the confusion.
|
There are two official terms that look similar, but are very different. Please be careful to avoid the confusion.
|
||||||
|
|
||||||
- Iterables are objects that implement the `Symbol.iterator` method, as described above.
|
- *Iterables* are objects that implement the `Symbol.iterator` method, as described above.
|
||||||
- Array-likes are objects that have indexes and `length`, so they look like arrays.
|
- *Array-likes* are objects that have indexes and `length`, so they look like arrays.
|
||||||
|
|
||||||
Sometimes they can both be applied. For instance, strings are both iterable and array-like.
|
Naturally, they can combine. For instance, strings are both iterable and array-like.
|
||||||
|
|
||||||
But an iterable may be not array-like and vise versa.
|
But an iterable may be not array-like and vise versa.
|
||||||
|
|
||||||
|
@ -171,7 +187,7 @@ for(let item of arrayLike) {}
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
But what they share in common -- both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we received such object and want to work with it as with an array.
|
What they share in common -- both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such object and want to work with it as with an array.
|
||||||
|
|
||||||
## Array.from
|
## Array.from
|
||||||
|
|
||||||
|
@ -186,10 +202,16 @@ let arrayLike = {
|
||||||
length: 2
|
length: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
let arr = Array.from(arrayLike);
|
*!*
|
||||||
|
let arr = Array.from(arrayLike); // (*)
|
||||||
|
*/!*
|
||||||
alert(arr.pop()); // World (method works)
|
alert(arr.pop()); // World (method works)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Array.from` at the line `(*)` takes the object, examines it for being an iterable or array-like, then makes a new array and copies there all items.
|
||||||
|
|
||||||
|
The same happens for an iterable:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// assuming that range is taken from the example above
|
// assuming that range is taken from the example above
|
||||||
let arr = Array.from(range);
|
let arr = Array.from(range);
|
||||||
|
@ -277,5 +299,3 @@ Objects that have indexed properties and `length` are called *array-like*. Such
|
||||||
If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract.
|
If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract.
|
||||||
|
|
||||||
`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. The optional arguments `mapFn` and `thisArg` allow to apply a function to each item.
|
`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. The optional arguments `mapFn` and `thisArg` allow to apply a function to each item.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
That's because `map.keys()` returns an iterable, but not an array.
|
||||||
|
|
||||||
|
We can convert it into an array using `Array.from`:
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let map = new Map();
|
||||||
|
|
||||||
|
map.set("name", "John");
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let keys = Array.from(map.keys());
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
keys.push("more");
|
||||||
|
|
||||||
|
alert(keys); // name, more
|
||||||
|
```
|
|
@ -0,0 +1,24 @@
|
||||||
|
importance: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Iterable keys
|
||||||
|
|
||||||
|
We want to get an array of `map.keys()` and go on working with it (apart from the map itself).
|
||||||
|
|
||||||
|
But there's a problem:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let map = new Map();
|
||||||
|
|
||||||
|
map.set("name", "John");
|
||||||
|
|
||||||
|
let keys = map.keys();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// Error: numbers.push is not a function
|
||||||
|
keys.push("more");
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Why? How can we fix the code to make `keys.push` work?
|
|
@ -0,0 +1,41 @@
|
||||||
|
The sane choice here is a `WeakSet`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let messages = [
|
||||||
|
{text: "Hello", from: "John"},
|
||||||
|
{text: "How goes?", from: "John"},
|
||||||
|
{text: "See you soon", from: "Alice"}
|
||||||
|
];
|
||||||
|
|
||||||
|
let readMessages = new WeakSet();
|
||||||
|
|
||||||
|
// two messages have been read
|
||||||
|
readMessages.add(messages[0]);
|
||||||
|
readMessages.add(messages[1]);
|
||||||
|
// readMessages has 2 elements
|
||||||
|
|
||||||
|
// ...let's read the first message again!
|
||||||
|
readMessages.add(messages[0]);
|
||||||
|
// readMessages still has 2 unique elements
|
||||||
|
|
||||||
|
// answer: was the message[0] read?
|
||||||
|
alert("Read message 0: " + readMessages.has(messages[0])); // true
|
||||||
|
|
||||||
|
messages.shift();
|
||||||
|
// now readMessages has 1 element (technically memory may be cleaned later)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it.
|
||||||
|
|
||||||
|
It cleans up itself automatically. The tradeoff is that we can't iterate over it. We can't get "all read messages" directly. But we can do it by iterating over all messages and filtering those that are in the set.
|
||||||
|
|
||||||
|
P.S. Adding a property of our own to each message may be dangerous if messages are managed by someone else's code, but we can make it a symbol to evade conflicts.
|
||||||
|
|
||||||
|
Like this:
|
||||||
|
```js
|
||||||
|
// the symbolic property is only known to our code
|
||||||
|
let isRead = Symbol("isRead");
|
||||||
|
messages[0][isRead] = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now even if someone else's code uses `for..in` loop for message properties, our secret flag won't appear.
|