Неявне приведення типів у Javascript (Implicit Coercion)
Tags: javascript, programming, tutorial
Published: 2023-07-30 09:01:27

Неявний примус у Javascript просто означає, що Javascript намагається примусити неочікуваний тип значення до очікуваного типу. Таким чином, ви можете передати рядок там, де він очікує число, об'єкт там, де він очікує рядок і т.д., і він спробує перетворити його до правильного типу.

```js
3 * "3" // 9
1 + "2" + 1 // 121

true + true // 2
10 - true // 9


const foo = {
  valueOf: () => 2
}
3 + foo // 5
4 * foo // 8

const bar = {
  toString: () => " promise is a boy :)"
}
1 + bar // "1 promise is a boy :)"


4 * [] // 0
4 * [2] // 8
4 + [2] // "42"
4 + [1, 2] // "41,2"
4 * [1, 2] // NaN

"string" ? 4 : 1 // 4
undefined ? 4 : 1 // 1
```

## Non-numeric values in numeric expressions

**Рядки**

Щоразу, коли ви передаєте рядок як операнд у числовому виразі, що містить будь-який з цих операторів: `-, *, /, %`, процес перетворення числа подібний до виклику вбудованої функції `Number` над значенням. Це досить просто: будь-який рядок, що містить лише цифрові символи, буде перетворено на його числовий еквівалент, але рядок, що містить нецифрові символи, поверне `NaN`. 

```js
3 * "3" // 3 * 3
3 * Number("3") // 3 * 3
Number("5") // 5

Number("1.") // 1
Number("1.34") // 1.34
Number("0") // 0
Number("012") // 12

Number("1,") // NaN
Number("1+1") // NaN
Number("1a") // NaN
Number("one") // NaN
Number("text") // NaN
```

**Випадок для оператора +**

Оператор +, на відміну від інших математичних операторів, виконує дві функції:

1. Математичне додавання;
2. Конкатенація рядків.

Коли операндом оператора + є рядок, Javascript замість того, щоб перетворювати рядок на число, перетворює число на рядок.

```js
// Concatenation
1 + "2" // "12"
1 + "js" // "1js"

// Addition
1 + 2 // 3
1 + 2 + 1 // 4

// Addition, then concatenation
1 + 2 + "1" // "31"
(1 + 2) + "1" // "31"

// Concatenation all through
1 + "2" + 1 // "121"
(1 + "2") + 1 // "121"
```

## Об'єкти

Більшість перетворень об'єктів Javascript зазвичай призводять до `[object Object]`.

```js
"name" + {} // "name[object Object]"
```

Кожен об'єкт JavaScript успадковує метод `toString`, який викликається щоразу, коли об'єкт потрібно перетворити в рядок. Значення, що повертається методом `toString`, використовується для таких операцій, як конкатенація рядків і математичні вирази.

```js
const foo = {}
foo.toString() // [object Object]

const baz = {
  toString: () => "I'm object baz"
}

baz + "!" // "I'm object baz!"
```

Якщо це математичний вираз, Javascript спробує перетворити значення, що повертається, в число, якщо воно не є числом.

```js
const foo = {
  toString: () => 4
}

2 * foo // 8
2 / foo // 0.5
2 + foo // 6
"four" + foo // "four4"

const baz = {
  toString: () => "four"
}

2 * baz // NaN
2 + baz // 2four

const bar = {
  toString: () => "2"
}

2 + bar // "22"
2 * bar // 4
```

**Об'єкти масивів**

Успадкований метод `toString` для масивів працює дещо інакше, він схожий на виклик методу `join` масиву без аргументів.

```js
[1,2,3].toString() // "1,2,3"
[1,2,3].join() // "1,2,3"
[].toString() // ""
[].join() // ""

"me" + [1,2,3] // "me1,2,3"
4 + [1,2,3] // "41,2,3"
4 * [1,2,3] // NaN
```

Отже, коли ви передаєте масив, де очікується рядок, Javascript об'єднує значення, що повертається методом `toString`, з другим операндом. Якщо очікується число, він намагається перетворити значення, що повертається, в число.

```js
4 * [] // 0
4 / [2] // 2

// Similar to
4 * Number([].toString())
4 * Number("")
4 * 0

//

4 / Number([2].toString())
4 / Number("2")
4 / 2
```

**True, False та ""**

```js
Number(true) // 1
Number(false) // 0
Number("") // 0

4 + true // 5
3 * false // 0
3 * "" // 0
3 + "" // "3"
```

**`valueOf` метод**

Також можна визначити метод `valueOf`, який буде використовуватися Javascript щоразу, коли ви передаєте об'єкт, в якому очікується рядкове або числове значення.

```js
const foo = {
  valueOf: () => 3
}

3 + foo // 6
3 * foo // 9
```

Якщо на об'єкті визначено обидва методи `toString` і `valueOf`, Javascript використовує метод `valueOf`.

```js
const bar = {
  toString: () => 2,
  valueOf: () => 5
}

"sa" + bar // "sa5"
3 * bar // 15
2 + bar // 7
```

Метод `valueOf` призначений для об'єктів, які мають представляти числове значення.

```js
const two = new Number(2)

two.valueOf() // 2
```

## Брехня і правда

Кожному значенню Javascript можна надати значення `true` або `false`. Примус до булевого значення `true` означає, що значення є істинним. Примус до булевого значення `false` означає, що значення є хибним.

В Javascript є декілька значень, які повертають хибні значення:

```js
if (false) // falsy
if (0) // falsy
if (null) // falsy
if (undefined) // falsy
if ("") // falsy
if (NaN) // falsy
if (-0) // falsy
```

Все інше - правда,

```js
if (-1) // truthy
if ("0") // truthy
if ({}) // truthy
```

Наведені вище фрагменти - це добре, але краще бути явним, коли намагаєшся визначити правдивість значення. Загалом, не покладайтеся на неявний примус Javascript, навіть якщо вам здається, що ви знаєте його досконало.
Замість фрагмента коду нижче,

```js
const counter = 2

if (counter)
```

Будь-який з наведених нижче способів є кращою практикою залежно від ваших вимог:

```js
if (counter === 2)

// Or

if (typeof counter === "number")
```

Це пов'язано з тим, що, наприклад, ви визначаєте функцію, яка має працювати з числами:

```js
const add = (number) => {
  if (!number) new Error("Only accepts arguments of type: number")
  // Your code
}
```

Отже, якщо викликати функцію add з 0, завжди отримаємо непередбачувану помилку:

```js
add(0) // Error: Only accepts arguments of type: number

// Better check

const add = (number) => {
  if (typeof number !== "number") new Error("Only accepts arguments of type: number")
  // Your code
}

add(0) // No error
```

## NaN

`NaN` - це особливе числове значення, яке не дорівнює самому собі.

```js
NaN === NaN // false

const notANumber = 3 * "a" // NaN

notANumber == notANumber // false
notANumber === notANumber // false
```

`NaN` - це єдине значення Javascript, яке не дорівнює самому собі. Тому ви можете перевірити наявність `NaN`, порівнявши його з самим собою.

```js
if (notANumber !== notANumber) // true
```

У ECMAScript 6 введено метод перевірки `NaN`, `Number.isNaN`.

```js
Number.isNaN(NaN) // true
Number.isNaN("name") // false
```

Остерігайтеся глобальної функції `isNaN`, вона намагається примусити аргумент, перш ніж перевірити, чи є він `NaN`.

```js
isNaN("name") // true
isNaN("1") // false
```

Глобальну функцію `isNaN` слід уникати, оскільки вона працює подібно до функції нижче:

```js
const coerceThenCheckNaN = (val) => {
  const coercedVal = Number(val)
  return coercedVal !== coercedVal ? true : false
}

coerceThenCheckNaN("1a") // true
coerceThenCheckNaN("1") // false
coerceThenCheckNaN("as") // true
coerceThenCheckNaN(NaN) // true
coerceThenCheckNaN(10) // false
```

## Оператор `new`

Оскільки 'new' створює екземпляр об'єкта 'Number', який порівнюється за посиланням. Значення не дорівнює посиланню на об'єкт.

```js
3 === new Number(3) // false
3 === Number(3) // true

typeof new Number(3) // object
typeof Number(3) // number
typeof 3 // number
```

Це більша частина неявного примусу.