work
This commit is contained in:
parent
0309cbfdd6
commit
0831ea228f
104 changed files with 423 additions and 875 deletions
|
@ -1,655 +0,0 @@
|
||||||
# Objects as dictionaries
|
|
||||||
|
|
||||||
Objects in JavaScript combine two functionalities.
|
|
||||||
|
|
||||||
1. First -- they are "associative arrays": a structure for storing keyed data.
|
|
||||||
2. Second -- they provide features for object-oriented programming like inheritance.
|
|
||||||
|
|
||||||
Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part.
|
|
||||||
|
|
||||||
Let's recap what we know about objects and add a bit more.
|
|
||||||
|
|
||||||
|
|
||||||
[cut]
|
|
||||||
|
|
||||||
## Object literals
|
|
||||||
|
|
||||||
We can imagine it as a cabinet with signed files. Every piece of data is stored in it's file. It's easy to find a file by it's name or add/remove a file.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
An empty object ("empty cabinet") can be created using one of two syntaxes:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = new Object(); // "object constructor" syntax
|
|
||||||
let user = {}; // "object literal" syntax
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Usually, the figure brackets `{...}` are used, they are more powerful shorter. The declaration is called an *object literal*.
|
|
||||||
|
|
||||||
We can set properties immediately:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30,
|
|
||||||
"likes birds": true // multiword property name must be quoted
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
In real code we quite often want to create an object with a property from a variable.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function makeUser(name, age) {
|
|
||||||
return {
|
|
||||||
name: name,
|
|
||||||
age: age;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = makeUser("John", 30);
|
|
||||||
alert(user.name); // John
|
|
||||||
```
|
|
||||||
|
|
||||||
There's a *property value shorthand* to make it shorter.
|
|
||||||
|
|
||||||
Instead of `name: name` we can just write `name`, like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function makeUser(name, age) {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
age;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can also combine normal properties and shorthands:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name, // same as name:name
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
````smart header="Trailing comma"
|
|
||||||
The last property may end with a comma:
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30*!*,*/!*
|
|
||||||
}
|
|
||||||
```
|
|
||||||
That is called a "trailing" or "hanging" comma. Makes it easier to add/move/remove properties, because all lines become alike.
|
|
||||||
````
|
|
||||||
|
|
||||||
## Accessing a property
|
|
||||||
|
|
||||||
To access a property, there are two syntaxes:
|
|
||||||
|
|
||||||
- The dot notation: `user.name`
|
|
||||||
- Square brackets: `user["name"]`
|
|
||||||
|
|
||||||
The dot notation requires the name to be a valid variable identifier, that is: no spaces, special chracters etc. Square brackets are more powerful, because they allow to specify an arbitrary string as a property name. Also, square brackets is the only choice when the name of the property is in a variable.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
let key = prompt("What do you want to know about the user?", "name");
|
|
||||||
|
|
||||||
// access by variable
|
|
||||||
alert( user[key] ); // John (if enter "name")
|
|
||||||
```
|
|
||||||
|
|
||||||
The square brackets mean: "take the property name from the variable".
|
|
||||||
|
|
||||||
Square brackets also can be used in an object literal.
|
|
||||||
|
|
||||||
That's called a *computed property*:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let fruit = prompt("Which fruit to buy?", "apple");
|
|
||||||
|
|
||||||
let bag = {
|
|
||||||
[fruit]: 5, // the name of the property is taken from the variable fruit
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( bag.apple ); // 5 if fruit="apple"
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, the object `bag` is created with a property with the name from `fruit` variable and the value `5`.
|
|
||||||
|
|
||||||
Essentially, that works the same as:
|
|
||||||
```js
|
|
||||||
let bag = {};
|
|
||||||
bag[fruit] = 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
We could have used a more complex expression inside square brackets or a quoted string. Anything that would return a property name:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let fruit = 'apple';
|
|
||||||
let bag = {
|
|
||||||
[ fruit.toUpperCase() ]: 5 // bag.APPLE = 5
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
````smart header="Property name must be either a string or a symbol"
|
|
||||||
We can only use strings or symbols as property names.
|
|
||||||
|
|
||||||
Other values are converted to strings, for instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let obj = {
|
|
||||||
0: "test" // same as "0": "test"
|
|
||||||
}
|
|
||||||
|
|
||||||
// bot alerts access the same property (the number 0 is converted to string "0")
|
|
||||||
alert( obj["0"] ); // test
|
|
||||||
alert( obj[0] ); // test (same property)
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
````smart header="Reserved words are allowed as property names"
|
|
||||||
A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
|
|
||||||
|
|
||||||
But for an object property, there's no such restruction. Any name is fine:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let obj = {
|
|
||||||
for: 1,
|
|
||||||
let: 2,
|
|
||||||
return: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
alert( obj.for + obj.let + obj.return ); // 6
|
|
||||||
```
|
|
||||||
|
|
||||||
Basically, any name is allowed, with one exclusion: `__proto__`.
|
|
||||||
|
|
||||||
The built-in property named `__proto__` has a special functionality (we'll cover it later), and it can't be set to a non-object value:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let obj = {};
|
|
||||||
obj.__proto__ = 5;
|
|
||||||
alert(obj.__proto__); // [object Object], didn't work as intended
|
|
||||||
```
|
|
||||||
|
|
||||||
As you we see from the code, an assignment to a primitive is ignored. If we want to store *arbitrary* (user-provided) keys, then such behavior can be the source of bugs and even vulnerabilities, because it's unexpected. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter <info:map-set-weakmap-weakset>, which supports arbitrary keys.
|
|
||||||
````
|
|
||||||
|
|
||||||
## Removing a property
|
|
||||||
|
|
||||||
There's a `delete` operator for that:
|
|
||||||
|
|
||||||
```js
|
|
||||||
delete user.name;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = {};
|
|
||||||
|
|
||||||
alert( user.noSuchProperty === undefined ); // true means "no such property"
|
|
||||||
```
|
|
||||||
|
|
||||||
There also exists a special operator `"in"` to check for the existance of a property.
|
|
||||||
|
|
||||||
The syntax is:
|
|
||||||
```js
|
|
||||||
"key" in object
|
|
||||||
```
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = { name: "John", age: 30 };
|
|
||||||
|
|
||||||
alert( "age" in user ); // true, user.age exists
|
|
||||||
alert( "blabla" in user ); // false, user.blabla doesn't exist
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that at the left side of `in` there must be a *property name*. That's usually a quoted string.
|
|
||||||
|
|
||||||
If we omit quotes, that would mean a variable containing the actual name to be tested. For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = { age: 30 };
|
|
||||||
|
|
||||||
let key = "age";
|
|
||||||
alert( key in user ); // true, takes the value of key and checks for such property
|
|
||||||
```
|
|
||||||
|
|
||||||
````smart header="Using \"in\" for properties that store `undefined`"
|
|
||||||
There is a case when `"=== undefined"` check fails, but the `"in"` operator works correctly.
|
|
||||||
|
|
||||||
It's when an object property stores `undefined`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let obj = {
|
|
||||||
test: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( obj.test ); // undefined, no such property?
|
|
||||||
|
|
||||||
alert( "test" in obj ); // true, the property does exist!
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
In the code above, the property `obj.test` technically exists. So the `in` operator works right.
|
|
||||||
|
|
||||||
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Loops
|
|
||||||
|
|
||||||
We've already seen one of the most popular loops: `for..in`
|
|
||||||
|
|
||||||
```js
|
|
||||||
for(let key in obj) {
|
|
||||||
// key iterates over object keys
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
But there are also ways to get keys, values or or key/value pairs as arrays:
|
|
||||||
|
|
||||||
- [Object.keys(obj)](mdn:js/Object/keys) -- returns the array of keys.
|
|
||||||
- [Object.values(obj)](mdn:js/Object/values) -- returns the array of values.
|
|
||||||
- [Object.entries(obj)](mdn:js/Object/entries) -- returns the array of `[key, value]` pairs.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- `Object.keys(user) = [name, age]`
|
|
||||||
- `Object.values(user) = ["John", 30]`
|
|
||||||
- `Object.entries(user) = [ ["name","John"], ["age",30] ]`
|
|
||||||
|
|
||||||
|
|
||||||
We can use `Object.values` to iterate over object values:
|
|
||||||
|
|
||||||
```js
|
|
||||||
for(let value of Object.values(obj)) {
|
|
||||||
// value iterates over object values
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here `Object.values(obj)` returns the array of properties, and `for..of` iterates over the array.
|
|
||||||
|
|
||||||
Also we can combine destructuring with `Object.entries` to iterate over key/value pairs:
|
|
||||||
|
|
||||||
```js
|
|
||||||
for(let [key, value] of Object.entries(obj)) {
|
|
||||||
// key,value iterate over properties
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The example of all 3 loops:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
// over keys
|
|
||||||
for(let key in user) {
|
|
||||||
alert(key); // name, then age
|
|
||||||
// can get the values with user[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
// over values
|
|
||||||
for(let value of Object.values(user)) {
|
|
||||||
alert(value); // John, then 30
|
|
||||||
}
|
|
||||||
|
|
||||||
// over key/value pairs
|
|
||||||
for(let [key, value] of Object.entries(user)) {
|
|
||||||
alert(key + ':' + value); // name:John, then age:30
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```smart header="The loops ignore symbolic properties"
|
|
||||||
All 3 forms of loops (and the given `Object` methods) ignore properties that use `Symbol(...)` as keys.
|
|
||||||
|
|
||||||
That's actually a wise thing, because symbols are created to make sure that the property can not be accessed accidentaly. There is a separate method named [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys (if we really know what we're doing and insist on that).
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Copying by reference
|
|
||||||
|
|
||||||
One of fundamental differences of objects vs primitives is that they are stored and copied "by reference".
|
|
||||||
|
|
||||||
Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value".
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let message = "Hello!";
|
|
||||||
let phrase = message;
|
|
||||||
```
|
|
||||||
|
|
||||||
As a result we have two independant variables, each one is storing the string `"Hello!"`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Objects are not like that.
|
|
||||||
|
|
||||||
**A variable stores not the object itself, but it's "address in memory", in other words "a reference" to it.**
|
|
||||||
|
|
||||||
Here's the picture for the object:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name: "John"
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Note that the object itself is stored somewhere in memory. The variable `user` has a "reference" to it.
|
|
||||||
|
|
||||||
**When an object variable is copied -- the reference is copied, the object is still single.**
|
|
||||||
|
|
||||||
We can easily grasp it if imagine an object as a cabinet, and a variable is a key to it. We can copy that key to another variable, the cabinet is still single.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js no-beautify
|
|
||||||
let user = { name: "John" };
|
|
||||||
|
|
||||||
let admin = user; // copy the reference
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we have two variables, each one with the reference to the same object:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Compare it with the primitives' picture. There's only one object, it's not copied.
|
|
||||||
|
|
||||||
Now can use any variable to access the cabinet and modify its contents:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = { name: 'John' };
|
|
||||||
|
|
||||||
let admin = user;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
admin.name = 'Pete'; // changed by the "admin" reference
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
|
|
||||||
```
|
|
||||||
|
|
||||||
Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified.
|
|
||||||
|
|
||||||
### Comparison by reference
|
|
||||||
|
|
||||||
Two object variabls are equal only when reference the same object:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let a = {};
|
|
||||||
let b = a; // copy the reference
|
|
||||||
|
|
||||||
alert( a == b ); // true, both variables reference the same object
|
|
||||||
```
|
|
||||||
|
|
||||||
We can also think of it like: the variables are "papers with address" of the objects. We copied the address from `a` to `b`. Then when we compare `a == b`, we compare the adresses. If they match, the equality is truthy.
|
|
||||||
|
|
||||||
In all other cases objects are non-equal, even if their content is the same.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let a = {};
|
|
||||||
let b = {}; // two independents object
|
|
||||||
|
|
||||||
alert( a == b ); // false
|
|
||||||
```
|
|
||||||
|
|
||||||
For unusual equality checks like: object vs a primitive (`obj == 5`), or an object less/greater than another object (`obj1 > obj2`), objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake.
|
|
||||||
|
|
||||||
## Cloning and Object.assign
|
|
||||||
|
|
||||||
What if we need to duplicate an object? Create an independant copy, a clone?
|
|
||||||
|
|
||||||
That's also doable, but a little bit more difficult, because there's no such method in Javascript. Actually, 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.
|
|
||||||
|
|
||||||
Like this:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let clone = {}; // the new empty object
|
|
||||||
|
|
||||||
// let's copy all user properties into it
|
|
||||||
for (let key in user) {
|
|
||||||
clone[key] = user[key];
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// now clone is a fully independant clone
|
|
||||||
clone.name = "Pete"; // changed the data in it
|
|
||||||
|
|
||||||
alert( user.name ); // still John
|
|
||||||
```
|
|
||||||
|
|
||||||
Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
|
|
||||||
|
|
||||||
The syntax is:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Object.assign(dest[, src1, src2, src3...])
|
|
||||||
```
|
|
||||||
|
|
||||||
- `dest` and other arguments (can be as many as needed) are objects
|
|
||||||
|
|
||||||
It copies the properties of all arguments starting from the 2nd (`src1`, `src2` etc) into the `dest`. Then it returns `dest`.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
```js
|
|
||||||
let user = { name: "John" };
|
|
||||||
|
|
||||||
let permissions1 = { canView: true };
|
|
||||||
let permissions2 = { canEdit: true };
|
|
||||||
|
|
||||||
// copies all properties from permissions1 and permissions2 into user
|
|
||||||
Object.assign(user, permissions1, permissions2);
|
|
||||||
|
|
||||||
// now user = { name: "John", canView: true, canEdit: true }
|
|
||||||
```
|
|
||||||
|
|
||||||
If `dest` already has the property with the same name, it's overwritten:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = { name: "John" };
|
|
||||||
|
|
||||||
// overwrite name, add isAdmin
|
|
||||||
Object.assign(user, { name: "Pete", isAdmin: true });
|
|
||||||
|
|
||||||
// now user = { name: "Pete", isAdmin: true }
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Here we can use it to replace the loop for cloning:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let clone = Object.assign({}, user);
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter.
|
|
||||||
|
|
||||||
Up to now we assumed that all properties of `user` are primitive. But actually properties can be references to other objects. What to do with them?
|
|
||||||
|
|
||||||
Like this:
|
|
||||||
```js run
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
sizes: {
|
|
||||||
height: 182,
|
|
||||||
width: 50
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( user.sizes.height ); // 182
|
|
||||||
```
|
|
||||||
|
|
||||||
Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` will be copied by reference. So `clone` and `user` will share the same sizes.
|
|
||||||
|
|
||||||
To fix that, we should examine the value of `user[key]` in the cloning loop and if it's an object, then replicate it's structure as well. That is called a "deep cloning".
|
|
||||||
|
|
||||||
There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](w3c.github.io/html/infrastructure.html#internal-structured-cloning-algorithm). We can use a ready implementation of it from the Javascript library [lodash](https://lodash.com). The method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
|
|
||||||
|
|
||||||
|
|
||||||
## Ordered like an object
|
|
||||||
|
|
||||||
Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order that they are added in it?
|
|
||||||
|
|
||||||
The short answer is: "ordered like an object": integer properties are sorted, others appear in creation order. The details follow.
|
|
||||||
|
|
||||||
As an example, let's consider an object with the phone codes:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let codes = {
|
|
||||||
"49": "Germany",
|
|
||||||
"41": "Switzerland",
|
|
||||||
"44": "Great Britain",
|
|
||||||
// ..,
|
|
||||||
"1": "USA"
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
for(let code in codes) {
|
|
||||||
alert(code); // 1, 41, 44, 49
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first.
|
|
||||||
|
|
||||||
But if we run the code, we see a totally different picture:
|
|
||||||
|
|
||||||
- USA (1) goes first
|
|
||||||
- then Switzerland (41) and so on.
|
|
||||||
|
|
||||||
That's because the iteration order is:
|
|
||||||
|
|
||||||
1. Integer properties in the ascending sort order go first.
|
|
||||||
2. String properties in the orders of their creation.
|
|
||||||
3. Symbol properties in the order of their creation.
|
|
||||||
|
|
||||||
The phone codes were sorted, because they are integer. That's why we see `1, 41, 44, 49`.
|
|
||||||
|
|
||||||
````smart header="Integer properties? What's that?"
|
|
||||||
By specification object property names are either strings or symbols. So an "integer property" actually means a string that can be converted to-from integer without a change.
|
|
||||||
|
|
||||||
So, "49" is an integer string, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
|
|
||||||
alert( String(Math.trunc(Number("+49"))) ); // "49", not same ⇒ not integer property
|
|
||||||
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same ⇒ not integer property
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
On the other hand, if the keys are non-integer, then they are listed as they appear, for instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let user = {
|
|
||||||
name: "John",
|
|
||||||
surname: "Smith"
|
|
||||||
};
|
|
||||||
user.age = 25; // add one more
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// non-integer properties are listed in the creation order
|
|
||||||
*/!*
|
|
||||||
for (let prop in user) {
|
|
||||||
alert( prop ); // name, surname, age
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus `"+"` sign before each code is enough.
|
|
||||||
|
|
||||||
Like this:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let codes = {
|
|
||||||
"+49": "Germany",
|
|
||||||
"+41": "Switzerland",
|
|
||||||
"+44": "Great Britain",
|
|
||||||
// ..,
|
|
||||||
"+1": "USA"
|
|
||||||
};
|
|
||||||
|
|
||||||
for(let code in codes) {
|
|
||||||
alert( +code ); // 49, 41, 44, 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now it works as intended.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Objects are associative arrays with several special features.
|
|
||||||
|
|
||||||
- Property keys are either strings or symbols.
|
|
||||||
- Values can be of any type.
|
|
||||||
|
|
||||||
Property access:
|
|
||||||
|
|
||||||
- Read/write uses the dot notation: `obj.property` or square brackets `obj["property"]/obj[varWithKey]`.
|
|
||||||
- The deletion is made via the `delete` operator.
|
|
||||||
- Existance check is made by the comparison vs `undefined` or via the `in` operator.
|
|
||||||
- Three forms of looping:
|
|
||||||
- `for(key in obj)` for the keys.
|
|
||||||
- `for(value of Object.values(obj))` for the values.
|
|
||||||
- `for([key,value] of Object.entries(obj))` for both.
|
|
||||||
|
|
||||||
- Ordering:
|
|
||||||
- Integer properties in sorted order first, then strings in creation order, then symbols in creation order.
|
|
||||||
- To keep the order for numeric properties, we can prepend them with `+` to make them look like non-numeric.
|
|
||||||
|
|
||||||
- Objects are assigned and copied by reference.
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Data structures
|
|
||||||
|
|
||||||
More data structures and more in-depth study of the known ones.
|
|
|
@ -14,7 +14,7 @@ let billion = 1000000000;
|
||||||
|
|
||||||
But in real life we usually dislike writing many zeroes. It's easy to mistype. Also we are lazy. We we usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billions 300 millions. The similar is true for other big numbers.
|
But in real life we usually dislike writing many zeroes. It's easy to mistype. Also we are lazy. We we usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billions 300 millions. The similar is true for other big numbers.
|
||||||
|
|
||||||
In JavaScript, we can do almost the same by appending the letter `e` to the number and specifying the zeroes count:
|
In JavaScript, we can do almost the same by appending the letter `"e"` to the number and specifying the zeroes count:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
|
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
|
||||||
|
@ -22,7 +22,7 @@ let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
|
||||||
alert( 7.3e9 ); // 7.3 billions (7,300,000,000)
|
alert( 7.3e9 ); // 7.3 billions (7,300,000,000)
|
||||||
```
|
```
|
||||||
|
|
||||||
In other words, `e` multiplies the number by `1` with the given zeroes count.
|
In other words, `"e"` multiplies the number by `1` with the given zeroes count.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
1e3 = 1 * 1000
|
1e3 = 1 * 1000
|
||||||
|
@ -30,13 +30,13 @@ In other words, `e` multiplies the number by `1` with the given zeroes count.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Now let's write something very small. Say, 1 microsecond is one millionth of a second:
|
Now let's write something very small. Say, 1 microsecond (one millionth of a second):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let ms = 0.000001;
|
let ms = 0.000001;
|
||||||
```
|
```
|
||||||
|
|
||||||
Also the same `e` can help. If we'd like not to write down the zeroes explicitly, the same number is:
|
Also the same `"e"` can help. If we'd like not to write down the zeroes explicitly, the same number is:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let ms = 1e-6; // six zeroes to the left from 1
|
let ms = 1e-6; // six zeroes to the left from 1
|
||||||
|
@ -44,7 +44,7 @@ let ms = 1e-6; // six zeroes to the left from 1
|
||||||
|
|
||||||
If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
|
If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
|
||||||
|
|
||||||
In other words, a negative number after `e` means a division by 1 with the given number of zeries:
|
In other words, a negative number after `"e"` means a division by 1 with the given number of zeries:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// -3 divides by 1 with 3 zeroes
|
// -3 divides by 1 with 3 zeroes
|
||||||
|
@ -75,11 +75,11 @@ let b = 0o377; // octal form of 255
|
||||||
alert( a == b ); // true, the same number 255 at both sides
|
alert( a == b ); // true, the same number 255 at both sides
|
||||||
```
|
```
|
||||||
|
|
||||||
There are only 3 numeral systems with such support. For other numeral systems we should use function `parseInt` (goes later in this chapter).
|
There are only 3 numeral systems with such support. For other numeral systems we should use function `parseInt` (later in this chapter).
|
||||||
|
|
||||||
## toString(base)
|
## toString(base)
|
||||||
|
|
||||||
There method `num.toString(base)` returns a string representation of `num` in the numeral system with the given `base`.
|
The method `num.toString(base)` returns a string representation of `num` in the numeral system with the given `base`.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```js run
|
```js run
|
||||||
|
@ -89,11 +89,11 @@ alert( num.toString(16) ); // ff
|
||||||
alert( num.toString(2) ); // 11111111
|
alert( num.toString(2) ); // 11111111
|
||||||
```
|
```
|
||||||
|
|
||||||
The `base` can vary from `2` to `36`.
|
The `base` can vary from `2` to `36`. By default it's `10`.
|
||||||
|
|
||||||
Most often use cases are:
|
Most often use cases are:
|
||||||
|
|
||||||
- **base=16** is used for colors, character encodings etc, digits can be `0..9` or `A..F`.
|
- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`.
|
||||||
- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`.
|
- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`.
|
||||||
- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base `36`:
|
- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base `36`:
|
||||||
|
|
||||||
|
@ -105,6 +105,8 @@ Most often use cases are:
|
||||||
Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it.
|
Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it.
|
||||||
|
|
||||||
If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.
|
If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.
|
||||||
|
|
||||||
|
Also could write `(123456).toString(36)`.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rounding
|
## Rounding
|
||||||
|
@ -268,7 +270,7 @@ JavaScript doesn't trigger an error in such case. It does the best to fit the nu
|
||||||
````
|
````
|
||||||
|
|
||||||
```smart header="Two zeroes"
|
```smart header="Two zeroes"
|
||||||
Another funny consequence of the internal representation is there are actually two zeroes: `0` and `-0`.
|
Another funny consequence of the internal representation is the existance of two zeroes: `0` and `-0`.
|
||||||
|
|
||||||
That's because a sign is represented by a single bit, so every number can be positive or negative, including the zero.
|
That's because a sign is represented by a single bit, so every number can be positive or negative, including the zero.
|
||||||
|
|
||||||
|
@ -294,7 +296,7 @@ They belong to the type `number`, but are not "normal" numbers, so there are spe
|
||||||
alert( isNaN("str") ); // true
|
alert( isNaN("str") ); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
But do we need the function? Can we just use the comparison `=== NaN`? Funny, but no. The value `NaN` is unique in that it does not equal anything including itself:
|
But do we need the function? Can we just use the comparison `=== NaN`? Sorry, but no. The value `NaN` is unique in that it does not equal anything including itself:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
alert( NaN === NaN ); // false
|
alert( NaN === NaN ); // false
|
||||||
|
@ -318,16 +320,18 @@ let num = +prompt("Enter a number", '');
|
||||||
alert( isFinite(num) );
|
alert( isFinite(num) );
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that an empty or a space-only string is treated as `0` in all numeric functions. If it's not what's needed, then additional checks are required.
|
Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
|
||||||
|
|
||||||
```smart header="Compare with `Object.is`"
|
```smart header="Compare with `Object.is`"
|
||||||
|
|
||||||
There is a special built-in method [Object.is](mdn:js/Object/is) to compare values in "even stricter way" than `===`.
|
There is a special built-in method [Object.is](mdn:js/Object/is) that compares values like `===`, but is more reliable for two edge cases:
|
||||||
|
|
||||||
The call `Object.is(value1, value2)` returns the same result as `value1 === value2` with two exceptions:
|
1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing.
|
||||||
|
2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, it rarely matters, but these values technically are different.
|
||||||
|
|
||||||
1. It can compare with `NaN`, e.g. `Object.is(NaN, NaN) === true`.
|
In all other cases, `Object.is(a, b)` is the same as `a === b`.
|
||||||
2. Values `0` and `-0` are different: `Object.is(0, -0) === false`.
|
|
||||||
|
This way of comparison is often used in Javascript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -408,8 +412,8 @@ There are more functions and constants in `Math`, including trigonometry, you ca
|
||||||
|
|
||||||
To write big numbers:
|
To write big numbers:
|
||||||
|
|
||||||
- Append `e` with the zeroes count to the number. Like: `123e6` is `123` with 6 zeroes.
|
- Append `"e"` with the zeroes count to the number. Like: `123e6` is `123` with 6 zeroes.
|
||||||
- A negative number after `e` causes the number to be divided by 1 with given zeroes. That's for one-millionth or such.
|
- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. That's for one-millionth or such.
|
||||||
|
|
||||||
For different numeral systems:
|
For different numeral systems:
|
||||||
|
|
33
1-js/6-data-types/03-object2/article.md
Normal file
33
1-js/6-data-types/03-object2/article.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
|
||||||
|
# Property descriptors
|
||||||
|
|
||||||
|
An object property is actually a more complex and tunable thing than just a "key-value" mapping.
|
||||||
|
|
||||||
|
There are two kinds of properties.
|
||||||
|
|
||||||
|
The first is *data properties*. All properties that we've seen yet are data properties.
|
||||||
|
|
||||||
|
Data properties have a `value` and three special attributes:
|
||||||
|
|
||||||
|
- **`writable`** -- if `true`, can be changed, otherwise it's read-only.
|
||||||
|
- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed.
|
||||||
|
- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not.
|
||||||
|
|
||||||
|
By default when a property is created, all attributes are `true`.
|
||||||
|
|
||||||
|
We can get the the information about an existing property using [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor):
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let user = {
|
||||||
|
name: "John"
|
||||||
|
};
|
||||||
|
|
||||||
|
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
|
||||||
|
|
||||||
|
alert( descriptor.value ); // John
|
||||||
|
alert( descriptor.writable ); // true
|
||||||
|
alert( descriptor.enumerable ); // true
|
||||||
|
alert( descriptor.configurable ); // true
|
||||||
|
```
|
|
@ -1,58 +1,21 @@
|
||||||
# Array methods
|
# Array methods
|
||||||
|
|
||||||
Arrays provide a lot of methods. In this chapter we'll study them more in-depth.
|
Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
## split and join
|
## Add/remove items
|
||||||
|
|
||||||
Here's the situation from the real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of names would be much more comfortable than a single string. How to get it?
|
We already know methods that add and remove items from the beginning or the end:
|
||||||
|
|
||||||
The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`.
|
- `arr.push(...items)`
|
||||||
|
- `arr.pop()`
|
||||||
|
- `arr.shift(...items)`
|
||||||
|
- `arr.unshift()`
|
||||||
|
|
||||||
In the example below, we split by a comma followed by space:
|
Here are few others.
|
||||||
|
|
||||||
```js run
|
### splice
|
||||||
let names = 'Bilbo, Gandalf, Nazgul';
|
|
||||||
|
|
||||||
let arr = names.split(', ');
|
|
||||||
|
|
||||||
for (let name of arr) {
|
|
||||||
alert( `A message to ${name}.` ); // A message to Bilbo (and other names)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `split` method has an optional second numeric argument -- a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
|
|
||||||
|
|
||||||
alert(arr); // Bilbo, Gandalf
|
|
||||||
```
|
|
||||||
|
|
||||||
````smart header="Split into letters"
|
|
||||||
The call to `split(s)` with an empty `s` would split the string into an array of letters:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let str = "test";
|
|
||||||
|
|
||||||
alert( str.split('') ); // t,e,s,t
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
The call [arr.join(str)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `str` beween them.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
|
|
||||||
|
|
||||||
let str = arr.join(';');
|
|
||||||
|
|
||||||
alert( str ); // Bilbo;Gandalf;Nazgul
|
|
||||||
```
|
|
||||||
|
|
||||||
## splice
|
|
||||||
|
|
||||||
How to delete an element from the array?
|
How to delete an element from the array?
|
||||||
|
|
||||||
|
@ -151,9 +114,223 @@ alert( arr ); // 1,2,3,4,5
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
## sort(fn)
|
### slice
|
||||||
|
|
||||||
The method [arr.sort()](mdn:js/Array/sort) sorts the array *at place*.
|
The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`.
|
||||||
|
|
||||||
|
The syntax is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
arr.slice(start, end)
|
||||||
|
```
|
||||||
|
|
||||||
|
It returns a new array where it copies all items start index `"start"` to `"end"` (not including `"end"`). Both `start` and `end` can be negative, in that case position from array end is assumed.
|
||||||
|
|
||||||
|
It works like `str.slice`, but makes subarrays instead of substrings.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let str = "test";
|
||||||
|
let arr = ["t", "e", "s", "t"];
|
||||||
|
|
||||||
|
alert( str.slice(1, 3) ); // es
|
||||||
|
alert( arr.slice(1, 3) ); // e,s
|
||||||
|
|
||||||
|
alert( str.slice(-2) ); // st
|
||||||
|
alert( arr.slice(-2) ); // s,t
|
||||||
|
```
|
||||||
|
|
||||||
|
### concat
|
||||||
|
|
||||||
|
The method [arr.concat](mdn:js/Array/concat) joins the array with other arrays and/or items.
|
||||||
|
|
||||||
|
The syntax is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
arr.concat(arg1, arg2...)
|
||||||
|
```
|
||||||
|
|
||||||
|
It accepts any number of arguments -- either arrays or values.
|
||||||
|
|
||||||
|
The result is a new array containing items from `arr`, then `arg1`, `arg2` etc.
|
||||||
|
|
||||||
|
If an argument is an array or has `Symbol.isConcatSpreadable` property, then all its elements are copied. Otherwise the argument itself is copied.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2];
|
||||||
|
|
||||||
|
// merge arr with [3,4]
|
||||||
|
alert( arr.concat([3, 4])); // 1,2,3,4
|
||||||
|
|
||||||
|
// merge arr with [3,4] and [5,6]
|
||||||
|
alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6
|
||||||
|
|
||||||
|
// merge arr with [3,4], then add values 5 and 6
|
||||||
|
alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6
|
||||||
|
```
|
||||||
|
|
||||||
|
Normally, it only copies elements from arrays ("spreads" them), array-likes or iterables are treated as regular values and added as a whole:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2];
|
||||||
|
|
||||||
|
let arrayLike = {
|
||||||
|
0: "something",
|
||||||
|
length: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
|
||||||
|
//[1, 2, arrayLike]
|
||||||
|
```
|
||||||
|
|
||||||
|
...But if an array-like has `Symbol.isConcatSpreadable` property, then its elements are added instead:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2];
|
||||||
|
|
||||||
|
let arrayLike = {
|
||||||
|
0: "something",
|
||||||
|
1: "else",
|
||||||
|
*!*
|
||||||
|
[Symbol.isConcatSpreadable]: true,
|
||||||
|
*/!*
|
||||||
|
length: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
alert( arr.concat(arrayLike) ); // 1,2,something,else
|
||||||
|
```
|
||||||
|
|
||||||
|
## Searching in array
|
||||||
|
|
||||||
|
These are methods to search for something in an array.
|
||||||
|
|
||||||
|
### indexOf/lastIndexOf and includes
|
||||||
|
|
||||||
|
The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
|
||||||
|
|
||||||
|
- `arr.indexOf(item, from)` looks for `item` starting from index `from`, and returns the index where it was found, otheriwse `-1`.
|
||||||
|
- `arr.lastIndexOf(item, from)` -- same, but looks from right to left.
|
||||||
|
- `arr.includes(item, from)` -- looks for `item` starting fron index `from`, returns `true` if found.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 0, false];
|
||||||
|
|
||||||
|
alert( arr.indexOf(0) ); // 1
|
||||||
|
alert( arr.indexOf(false) ); // 2
|
||||||
|
alert( arr.indexOf(null) ); // -1
|
||||||
|
|
||||||
|
alert( arr.includes(1) ); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
|
||||||
|
|
||||||
|
If we wan to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred.
|
||||||
|
|
||||||
|
|
||||||
|
### find and findIndex
|
||||||
|
|
||||||
|
Imagine we have an array of objects. How do we find an object with the specific condition?
|
||||||
|
|
||||||
|
Here the [arr.find](mdn:js/Array/find) method comes in handy.
|
||||||
|
|
||||||
|
The syntax is:
|
||||||
|
```js
|
||||||
|
let result = arr.find(function(item, index, array) {
|
||||||
|
// should return true if the item is what we are looking for
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The function is called repetitively for each element of the array:
|
||||||
|
|
||||||
|
- `item` is the element.
|
||||||
|
- `index` is its index.
|
||||||
|
- `array` is the array itself.
|
||||||
|
|
||||||
|
If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned.
|
||||||
|
|
||||||
|
For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let users = [
|
||||||
|
{id: 1, name: "John"},
|
||||||
|
{id: 2, name: "Pete"},
|
||||||
|
{id: 3, name: "Mary"}
|
||||||
|
];
|
||||||
|
|
||||||
|
let user = users.find(item => item.id == 1);
|
||||||
|
|
||||||
|
alert(user.name); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
In real life arrays of objects is a common thing, so the `find` method is very useful.
|
||||||
|
|
||||||
|
Note that in the example we provide to `find` a single-argument function `item => item.id == 1`. Other parameters of `find` are rarely used.
|
||||||
|
|
||||||
|
The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself.
|
||||||
|
|
||||||
|
### filter
|
||||||
|
|
||||||
|
The `find` method looks for a single (first) element that makes the function return `true`.
|
||||||
|
|
||||||
|
If there may be many, we can use [arr.filter(fn)](mdn:js/Array/filter).
|
||||||
|
|
||||||
|
The syntax is roughly the same as `find`, but it returns an array of matching elements:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let results = arr.filter(function(item, index, array) {
|
||||||
|
// should return true if the item passes the filter
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let users = [
|
||||||
|
{id: 1, name: "John"},
|
||||||
|
{id: 2, name: "Pete"},
|
||||||
|
{id: 3, name: "Mary"}
|
||||||
|
];
|
||||||
|
|
||||||
|
// returns array of the first two users
|
||||||
|
let someUsers = users.filter(item => item.id < 3);
|
||||||
|
|
||||||
|
alert(someUsers.length); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transform an array
|
||||||
|
|
||||||
|
This section is about the methods transforming or reordering the array.
|
||||||
|
|
||||||
|
|
||||||
|
### map
|
||||||
|
|
||||||
|
The [arr.map](mdn:js/Array/map) method is one of the most useful and often used.
|
||||||
|
|
||||||
|
The syntax is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let result = arr.map(function(item, index, array) {
|
||||||
|
// returns the new value instead of item
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It calls the function for each element of the array and returns the array of results.
|
||||||
|
|
||||||
|
For instance, here we transform each element into its length:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length)
|
||||||
|
alert(lengths); // 5,7,6
|
||||||
|
```
|
||||||
|
|
||||||
|
### sort(fn)
|
||||||
|
|
||||||
|
The method [arr.sort](mdn:js/Array/sort) sorts the array *at place*.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -241,131 +418,72 @@ Remember [arrow functions](info:function-expression#arrow-functions)? We can use
|
||||||
arr.sort( (a, b) => a - b );
|
arr.sort( (a, b) => a - b );
|
||||||
```
|
```
|
||||||
|
|
||||||
This would work exactly the same as a longer function expression above.
|
This works exactly the same as a longer function expression above.
|
||||||
````
|
````
|
||||||
|
|
||||||
## indexOf/lastIndexOf and includes
|
### reverse
|
||||||
|
|
||||||
The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) are the same as their string counterparts.
|
The method [arr.reverse](mdn:js/Array/reverse) creates a new array with the reverse order.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let arr = [1, 0, false];
|
let arr = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
alert( arr.indexOf(0) ); // 1
|
alert( arr.reverse() ); // 5,4,3,2,1
|
||||||
alert( arr.indexOf(false) ); // 2
|
|
||||||
alert( arr.indexOf(null) ); // -1
|
|
||||||
|
|
||||||
alert( arr.includes(1) ); // true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
|
The original array is not modified.
|
||||||
|
|
||||||
|
### split and join
|
||||||
|
|
||||||
## find
|
Here's the situation from the real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of names would be much more comfortable than a single string. How to get it?
|
||||||
|
|
||||||
Imagine we have an array of objects. How do we find an object with the specific condition?
|
The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`.
|
||||||
|
|
||||||
Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy.
|
In the example below, we split by a comma followed by space:
|
||||||
|
|
||||||
The syntax is:
|
|
||||||
```js
|
|
||||||
let result = arr.find(function(item, index, array) {
|
|
||||||
// should return true if the item is what we are looking for
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The function is called repetitively for each element of the array:
|
|
||||||
|
|
||||||
- `item` is the element.
|
|
||||||
- `index` is its index.
|
|
||||||
- `array` is the array itself.
|
|
||||||
|
|
||||||
If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned.
|
|
||||||
|
|
||||||
For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let users = [
|
let names = 'Bilbo, Gandalf, Nazgul';
|
||||||
{id: 1, name: "John"},
|
|
||||||
{id: 2, name: "Pete"},
|
|
||||||
{id: 3, name: "Mary"}
|
|
||||||
];
|
|
||||||
|
|
||||||
let user = users.find(item => item.id == 1);
|
let arr = names.split(', ');
|
||||||
|
|
||||||
alert(user.name); // John
|
for (let name of arr) {
|
||||||
|
alert( `A message to ${name}.` ); // A message to Bilbo (and other names)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In real life arrays of objects is a common thing, so the `find` method is very useful.
|
The `split` method has an optional second numeric argument -- a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though:
|
||||||
|
|
||||||
Note that in the example we provide to `find` a single-argument function `item => item.id == 1`. It could be longer, like `(item, index, array) => ...`, but the additional parameters are optional. In fact, they are rarely used.
|
|
||||||
|
|
||||||
|
|
||||||
## filter
|
|
||||||
|
|
||||||
The `find` method looks for a single (first) element that makes the function return `true`.
|
|
||||||
|
|
||||||
If there may be multiple, we can use [arr.filter(fn)](mdn:js/Array/filter).
|
|
||||||
|
|
||||||
The syntax is roughly the same, but it returns an array of matching elements:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let users = [
|
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
|
||||||
{id: 1, name: "John"},
|
|
||||||
{id: 2, name: "Pete"},
|
|
||||||
{id: 3, name: "Mary"}
|
|
||||||
];
|
|
||||||
|
|
||||||
// returns array of the first two users
|
alert(arr); // Bilbo, Gandalf
|
||||||
let someUsers = users.filter(item => item.id < 3);
|
|
||||||
|
|
||||||
alert(someUsers.length); // 2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## forEach
|
````smart header="Split into letters"
|
||||||
|
The call to `split(s)` with an empty `s` would split the string into an array of letters:
|
||||||
The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array.
|
|
||||||
|
|
||||||
The syntax:
|
|
||||||
```js
|
|
||||||
arr.forEach(function(item, index, array) {
|
|
||||||
// ... do something with item
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
For instance, this shows each element of the array:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// for each element call alert
|
let str = "test";
|
||||||
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
|
|
||||||
|
alert( str.split('') ); // t,e,s,t
|
||||||
```
|
```
|
||||||
|
````
|
||||||
|
|
||||||
And this code is more elaborate about their positions in the target array:
|
The call [arr.join(str)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `str` beween them.
|
||||||
|
|
||||||
```js run
|
|
||||||
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
|
|
||||||
alert(`${item} is at index ${index} in ${array}`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The result of the function (if it returns any) is thrown away and ignored.
|
|
||||||
|
|
||||||
## map
|
|
||||||
|
|
||||||
The [arr.map](mdn:js/Array/map) is used to transform the array.
|
|
||||||
|
|
||||||
It calls a function for each element of the array and returns the array of the results.
|
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length)
|
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
|
||||||
alert(lengths); // 5,7,6
|
|
||||||
|
let str = arr.join(';');
|
||||||
|
|
||||||
|
alert( str ); // Bilbo;Gandalf;Nazgul
|
||||||
```
|
```
|
||||||
|
|
||||||
## reduce/reduceRight
|
### reduce/reduceRight
|
||||||
|
|
||||||
When we need to iterate over an array -- we can use `forEach`.
|
When we need to iterate over an array -- we can use `forEach`.
|
||||||
|
|
||||||
|
@ -448,6 +566,35 @@ But such use requires an extreme care. If the array is empty, then `reduce` call
|
||||||
The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left.
|
The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Iterate: forEach
|
||||||
|
|
||||||
|
The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array.
|
||||||
|
|
||||||
|
The syntax:
|
||||||
|
```js
|
||||||
|
arr.forEach(function(item, index, array) {
|
||||||
|
// ... do something with item
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance, this shows each element of the array:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
// for each element call alert
|
||||||
|
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
|
||||||
|
```
|
||||||
|
|
||||||
|
And this code is more elaborate about their positions in the target array:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
|
||||||
|
alert(`${item} is at index ${index} in ${array}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The result of the function (if it returns any) is thrown away and ignored.
|
||||||
|
|
||||||
## Array.isArray
|
## Array.isArray
|
||||||
|
|
||||||
Arrays do not form a separate language type. They are based on objects.
|
Arrays do not form a separate language type. They are based on objects.
|
||||||
|
@ -467,30 +614,67 @@ alert(Array.isArray({})); // false
|
||||||
alert(Array.isArray([])); // true
|
alert(Array.isArray([])); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
The full syntax is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let result = arr.find(func, thisArg);
|
||||||
|
let results = arr.filter(func, thisArg);
|
||||||
|
// etc, thisArg goes after the function
|
||||||
|
```
|
||||||
|
|
||||||
|
It is used sparingly, but we have to cover it here for the sake of completeness.
|
||||||
|
|
||||||
|
The value of `thisArg` parameter becomes `this` for the function.
|
||||||
|
|
||||||
|
For instance, here we use an object method as a filter:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let user = {
|
||||||
|
age: 18,
|
||||||
|
younger(otherUser) {
|
||||||
|
return otherUser.age < this.age;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let users = [
|
||||||
|
{age: 12},
|
||||||
|
{age: 16},
|
||||||
|
{age: 32}
|
||||||
|
];
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// find all users younger than user
|
||||||
|
let youngerUsers = users.filter(user.younger, user);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert(youngerUsers.length); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
In the call above, we use `user.younger` as a filter and also provide `user` as the context for it. If we did't provide the context, `users.filter(user.younger)` would call `user.younger` as a standalone function, with `this=undefined`. That would be an instant error.
|
||||||
|
|
||||||
## Other methods
|
## Other methods
|
||||||
|
|
||||||
We covered the most useful methods. But there are other too, for instance:
|
We covered the most useful methods. But there are few others:
|
||||||
|
|
||||||
- [arr.slice(begin, end)](mdn:js/Array/slice) copies the portion of the array.
|
|
||||||
|
|
||||||
It creates a new array and copies elements of `arr` from `begin` to `end` into it. Both arguments are optional: if `end` is omitted, the copying goes till the end, if `begin` is omitted, then from the very beginning. So `arr.slice()` makes a full copy.
|
|
||||||
|
|
||||||
- [arr.concat(arr2, arr3...)](mdn:js/Array/concat) joins the arrays.
|
|
||||||
|
|
||||||
It creates a new array from elements of `arr`, then appends elements from `arr2` to it, then appends elements from `arr3` and so on.
|
|
||||||
|
|
||||||
- [arr.reverse()](mdn:js/Array/reverse) reverses the array.
|
|
||||||
|
|
||||||
It creates a new array with elements from `arr` in the reverse order.
|
|
||||||
|
|
||||||
- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array.
|
- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array.
|
||||||
|
|
||||||
The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`.
|
The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`.
|
||||||
|
|
||||||
|
- [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`.
|
||||||
|
|
||||||
|
- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing).
|
||||||
|
|
||||||
|
- [arr.keys()](mdn:js/Array/keys) -- returns iterable over array indexes.
|
||||||
|
|
||||||
|
- [arr.entries()](mdn:js/Array/entries) -- return iterables over array index/value pairs.
|
||||||
|
|
||||||
These and other methods are also listed in the [manual](mdn:js/Array).
|
These and other methods are also listed in the [manual](mdn:js/Array).
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Most often methods:
|
Most often methods:
|
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 |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue