Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,19 @@ let str = "Hello";

str.test = 5; // (*)

alert(str.test);
alert(str.test);
```

There may be two kinds of result:
1. `undefined`
2. An error.
Depending on whether you have `use strict` or not, the result may be:
1. `undefined` (no strict mode)
2. An error (strict mode).

Why? Let's replay what's happening at line `(*)`:

1. When a property of `str` is accessed, a "wrapper object" is created.
2. The operation with the property is carried out on it. So, the object gets the `test` property.
3. The operation finishes and the "wrapper object" disappears.

So, on the last line, `str` has no trace of the property. A new wrapper object for every object operation on a string.

Some browsers though may decide to further limit the programmer and disallow to assign properties to primitives at all. That's why in practice we can also see errors at line `(*)`. It's a little bit farther from the specification though.
2. In strict mode, writing into it is an error.
3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears, so in the last line `str` has no trace of the property.

**This example clearly shows that primitives are not objects.**

They just can not store data.

All property/method operations are performed with the help of temporary objects.

They can't store additional data.
24 changes: 11 additions & 13 deletions 1-js/05-data-types/01-primitives-methods/article.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# Methods of primitives

JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects.

They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer).
JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer).

Let's look at the key distinctions between primitives and objects.

A primitive

- Is a value of a primitive type.
- There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`.
- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`.

An object

- Is capable of storing multiple values as properties.
- Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript; functions, for example, are objects.
- Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript: functions, for example, are objects.

One of the best things about objects is that we can store a function as one of its properties.

Expand All @@ -35,7 +33,7 @@ Many built-in objects already exist, such as those that work with dates, errors,

But, these features come with a cost!

Objects are "heavier" than primitives. They require additional resources to support the internal machinery. But as properties and methods are very useful in programming, JavaScript engines try to optimize them to reduce the additional burden.
Objects are "heavier" than primitives. They require additional resources to support the internal machinery.

## A primitive as an object

Expand All @@ -48,11 +46,11 @@ The solution looks a little bit awkward, but here it is:

1. Primitives are still primitive. A single value, as desired.
2. The language allows access to methods and properties of strings, numbers, booleans and symbols.
3. When this happens, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.
3. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.

The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods.

For instance, there exists a method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized string.
For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`.

Here's how it works:

Expand Down Expand Up @@ -84,25 +82,25 @@ We'll see more specific methods in chapters <info:number> and <info:string>.


````warn header="Constructors `String/Number/Boolean` are for internal use only"
Some languages like Java allow us to create "wrapper objects" for primitives explicitly using a syntax like `new Number(1)` or `new Boolean(false)`.
Some languages like Java allow us to explicitly create "wrapper objects" for primitives using a syntax like `new Number(1)` or `new Boolean(false)`.

In JavaScript, that's also possible for historical reasons, but highly **unrecommended**. Things will go crazy in several places.

For instance:

```js run
alert( typeof 1 ); // "number"
alert( typeof 0 ); // "number"

alert( typeof new Number(1) ); // "object"!
alert( typeof new Number(0) ); // "object"!
```

And because what follows, `zero`, is an object, the alert will show up:
Objects are always truthy in `if`, so here the alert will show up:

```js run
let zero = new Number(0);

if (zero) { // zero is true, because it's an object
alert( "zero is truthy?!?" );
alert( "zero is truthy!?!" );
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("readNumber", function() {
assert.strictEqual(readNumber(), 0);
});

it("continues the loop unti meets a number", function() {
it("continues the loop until meets a number", function() {
prompt.onCall(0).returns("not a number");
prompt.onCall(1).returns("not a number again");
prompt.onCall(2).returns("1");
Expand All @@ -35,4 +35,4 @@ describe("readNumber", function() {
assert.isNull(readNumber());
});

});
});
6 changes: 3 additions & 3 deletions 1-js/05-data-types/02-number/9-random-int-min-max/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Any number from the interval `min..max` must appear with the same probability.
Examples of its work:

```js
alert( random(1, 5) ); // 1
alert( random(1, 5) ); // 3
alert( random(1, 5) ); // 5
alert( randomInteger(1, 5) ); // 1
alert( randomInteger(1, 5) ); // 3
alert( randomInteger(1, 5) ); // 5
```

You can use the solution of the [previous task](info:task/random-min-max) as the base.
39 changes: 20 additions & 19 deletions 1-js/05-data-types/02-number/article.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Numbers

All numbers in JavaScript are stored in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), also known as "double precision".
In modern JavaScript, there are two types of numbers:

Let's recap and expand upon what we currently know about them.
1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter.

2. BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed <code>2<sup>53</sup></code> or be less than <code>-2<sup>53</sup></code>. As bigints are used in few special areas, we devote them a special chapter <info:bigint>.

So here we'll talk about regular numbers. Let's expand our knowledge of them.

## More ways to write a number

Expand All @@ -12,7 +16,7 @@ Imagine we need to write 1 billion. The obvious way is:
let billion = 1000000000;
```

But in real life we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.
But in real life, we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.

In JavaScript, we shorten a number by appending the letter `"e"` to the number and specifying the zeroes count:

Expand All @@ -29,14 +33,13 @@ In other words, `"e"` multiplies the number by `1` with the given zeroes count.
1.23e6 = 1.23 * 1000000
```


Now let's write something very small. Say, 1 microsecond (one millionth of a second):

```js
let ms = 0.000001;
```

Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say:
Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as:

```js
let ms = 1e-6; // six zeroes to the left from 1
Expand Down Expand Up @@ -177,7 +180,7 @@ There are two ways to do so:

## Imprecise calculations

Internally, a number is represented in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.
Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.

If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:

Expand All @@ -201,19 +204,19 @@ Strange! What is it then if not `0.3`?
alert( 0.1 + 0.2 ); // 0.30000000000000004
```

Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their chart. The order total will be `$0.30000000000000004`. That would surprise anyone.
Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone.

But why does this happen?

A number is stored in memory in its binary form, a sequence of ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form.
A number is stored in memory in its binary form, a sequence of bits - ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form.

In other words, what is `0.1`? It is one divided by ten `1/10`, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`.

So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction.

There's just no way to store *exactly 0.1* or *exactly 0.2* using the binary system, just like there is no way to store one-third as a decimal fraction.

The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", so the number shows up as `0.3`. But beware, the loss still exists.
The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", but it exists.

We can see this in action:
```js run
Expand Down Expand Up @@ -271,13 +274,11 @@ JavaScript doesn't trigger an error in such events. It does its best to fit the
```smart header="Two zeroes"
Another funny consequence of the internal representation of numbers is the existence 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 a zero.
That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero.

In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
```



## Tests: isFinite and isNaN

Remember these two special numeric values?
Expand Down Expand Up @@ -323,10 +324,10 @@ Please note that an empty or a space-only string is treated as `0` in all numeri

```smart header="Compare with `Object.is`"

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:
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:

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.
2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's true, because internally the number has a sign bit that may be different even if all other bits are zeroes.

In all other cases, `Object.is(a, b)` is the same as `a === b`.

Expand Down Expand Up @@ -409,15 +410,15 @@ There are more functions and constants in `Math` object, including trigonometry,

## Summary

To write big numbers:
To write numbers with many 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.
- Append `"e"` with the zeroes count to the number. Like: `123e6` is the same as `123` with 6 zeroes `123000000`.
- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionths).

For different numeral systems:

- Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems
- `parseInt(str, base)` parses an integer from any numeral system with base: `2 ≤ base ≤ 36`.
- Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems.
- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`.
- `num.toString(base)` converts a number to a string in the numeral system with the given `base`.

For converting values like `12pt` and `100px` to a number:
Expand Down
4 changes: 2 additions & 2 deletions 1-js/05-data-types/03-string/1-ucfirst/solution.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ But we can make a new string based on the existing one, with the uppercased firs
let newStr = str[0].toUpperCase() + str.slice(1);
```

There's a small problem though. If `str` is empty, then `str[0]` is undefined, so we'll get an error.
There's a small problem though. If `str` is empty, then `str[0]` is `undefined`, and as `undefined` doesn't have the `toUpperCase()` method, we'll get an error.

There are two variants here:

Expand All @@ -15,7 +15,7 @@ There are two variants here:

Here's the 2nd variant:

```js run
```js run demo
function ucFirst(str) {
if (!str) return str;

Expand Down
2 changes: 1 addition & 1 deletion 1-js/05-data-types/03-string/1-ucfirst/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ importance: 5

---

# Uppercast the first character
# Uppercase the first character

Write a function `ucFirst(str)` that returns the string `str` with the uppercased first character, for instance:

Expand Down
2 changes: 1 addition & 1 deletion 1-js/05-data-types/03-string/2-check-spam/solution.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
To make the search case-insensitive, let's bring the string to lower case and then search:

```js run
```js run demo
function checkSpam(str) {
let lowerStr = str.toLowerCase();

Expand Down
2 changes: 1 addition & 1 deletion 1-js/05-data-types/03-string/2-check-spam/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ importance: 5

# Check for spam

Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false.
Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false`.

The function must be case-insensitive:

Expand Down
Loading