Understand JavaScript Abstract Operations in 7 Minutes.

Tags: javascript, coercion, abstractoperations, javascripttypes
Published: 2021-02-01 12:19:09

To help you understand better, let’s quickly revisit types in JavaScript. Variables in JavaScript do not have types; values do. JavaScript has eight basic value data types, and they are divided into two categories, primitive and non-primitive.

**Primitive Types:**

- Undefined
- Null
- Boolean
- String
- Symbol
- Number
- BigInt

**Non-Primitive Types (Reference types):**

- Object
- Array
- Function


    >  `Arrays`  and  `functions`  are subtypes of the  `object`  type.
    

One major difference between primitive and non-primitive values is that primitive values are immutable after creation while non-primitive values are mutable. 

Let’s take an example:

```js
// Let's alter the value of the string assigned to the variable person.
let person = "ifeoma"
// Here it simply made a copy and then altered the copied value.
console.log(person.toUpperCase()) // IFEOMA
// It didn't change the original value.
console.log(person) //  ifeoma
```   

From the example above, when we tried to alter the value, it only made a copy of the variable `person`  and changed it, but it didn’t change the already existing  `string`  value because it is a primitive.

On the other hand, the variable assigned to a primitive value can be changed. Therefore, it can be reassigned so that it points to a new value but the existing value it holds cannot be changed.

```js
let person = "ifeoma"
person = "sylvia"
console.log(person)  // sylvia
```
In the example above, we assigned a new  `string`  value to the variable  `person`  so that it no longer points to the initial  `string`  value  `ifeoma` .

Let’s try to mutate a non-primitive:

```js
let array = [ "Jay Pritchet", "Phil Dunphy" ]
let array2 = array
array.push("Claire Pritchet")
console.log(array2) // [ "Jay Pritchet", "Phil Dunphy", "Claire Pritchet" ]
```

In the above example, we made  `array`  a reference to  `array2` . Emphasis on reference, which means that even after we modified the data in `array` by adding a new item to it, logging  `array2`  shows the new item added to  `array` . 
This is because the variable `array2` is referencing the address of the variable `array`. 
This behavior is expected from all non-primitive value types.

Another difference between primitives and non-primitives is that primitives are stored by value while non-primitives are stored by reference.

The  `typeOf`  operator is a built-in utility used to check the type of value assigned to a javascript variable. 

> The  `typeOf`   operator always returns a string type.

Let’s take a look at how values are changed from one type to another.


## Coercion

Coercion refers to the process of converting values from one type to another(such as  `string`  to  `number` ).
Using inbuilt functions(`Number()`, `String()` etc.) you can be obvious about your intention to convert a value from one type to another(explicit coercion) or let Javascript automatically handle the conversion for you(Implicit coercion). 
 
Coercion always results in either  `strings` ,  `numbers` , or  `booleans` . Understanding coercion will help you avoid problems that can occur in your code.
Let's see some examples.

**Implicit Coercion** 

```js
5 - "1" // 4  JavaScript coerced the string 1 to a number.
10 * false // 0  Javascript coerced the boolean false to 0.
10 + true // 11  The boolean true is coerced to a number 1.
```

**Explicit Coercion** 

```js
Number('5') - Number('2') // 3 Here we are explicitly converting both strings to numbers first using the Number() method.
```

To fully understand how coercion happens, we need to talk about **Abstract Operations.** 

> Abstract Operations are the fundamental building block that makes up how we deal with type conversion. - Kyle Simpson                       


## Abstract Operations

According to the ECMAScript specification, abstract operations are not part of the language specification, but they are responsible for performing type conversion in Javascript. Whenever coercion (implicit or explicit) occurs, one or more internal operations, known as abstract operations, are performed. 

We will look at these four primary abstract operations, but you can find the rest of them in the [spec.](https://tc39.es/ecma262/#sec-abstract-operations)


1. ToPrimitive()
2. ToString()
3. ToNumber()
4. ToBoolean() 


## ToPrimitive()

When a non-primitive or reference value is used in the context where a primitive is required, the JavaScript engine calls the  `ToPrimitive()`  abstract operation. 

When converting non-primitive types to primitives, the abstract operation ToPrimitive() is invoked to handle the operation.

Let's see how non-primitive values are converted to primitives.

The spec informs us that the  `ToPrimitive()`  abstract operation takes two arguments. 

1.  An **input**
2. An optional ***PreferredType*** hint


> The PreferredType hint can be either one of these -  `string` ,  `number` ,  `default` .                      

If you are performing a numeric operation and the  `ToPrimitive()`  operation is invoked, `number` will be sent as the hint. If you are working with strings, it will send  `string`  as the hint. 
When  `ToPrimitive()` is called with no hint present, it'll send  `default`  as a hint, and this will behave as if the hint were  `number`  (unless it's a  `Date`  which defaults to  `string` ).

If the argument is already a primitive value, then it will be returned without conversion. Let's take a look at how the ToPrimitive algorithm works.

There are two methods available on every object type used to convert them from non-primitives to primitives:

1. valueOf() — This is to retrieve the primitive value associated with an object.
2. toString() 

**Number Algorithm**
If the hint is  `number` , it calls the `valueOf()`  function first, and if the returned value is primitive, it'll use it. If the object has no primitive value,  `valueOf()`   returns the object back then the  `toString()`  function gets called. Its value will be used if it is primitive; otherwise, it would result in a type error.

**String Algorithm**
If the hint is  `string` , the order is reversed compared to the number algorithm. It calls the non-primitive  `toString()`  function first, and if it gets a string representation, it'll just use it; otherwise, it'll try the  `valueOf()` method to see if the object has any primitive value.

**Default Algorithm**
If no hint is sent, it sets the default hint to  `number` **,** or it is set to  `string`  if it is a  `Date` . 


> The algorithms within JavaScript are inherently recursive. This means that if the  `ToPrimitive()`  operation gets invoked and the return result is not a primitive, it will keep getting invoked until it returns a primitive or an error.   


## ToString()

This abstract operation takes any value and converts it to a representation of the value in  `string`  form.

| Argument  | Result                       |
| --------- | ---------------------------- |
| Null      | “null”                       |
| Undefined | “undefined                   |
| true      | “true”                       |
| false     | “false”                      |
| “Hi”      | “Hi”                         |
| Symbol    | Throw a TypeError exception. |
|           |                              |


As seen above, built-in primitives have natural stringification, but if the  `ToString()`  operation is called on an  `object`  type, it will invoke the `ToPrimitive()` operation, and pass `string` as the hint.

As explained earlier, this will call the  `toString()`  first, and if it gets a `string` representation, it'll just use it; otherwise, it'll try the  `valueOf()` method.

By default, regular JavaScript objects have their built-in  `toString()`  method (located in [**Object.prototype.toString()**](https://tc39.es/ecma262/#sec-object.prototype.tostring)) that is called when an object is used in a manner in which a `string` is expected and this will return their internal [[Class]] property (e.g [object Object]). 
 
Unless you specify your `toString()` method, if you use an object in a string-like way, the `toString()` method on its object prototype will be called. This will return a `string` with the [object Type] format where  `Type`  is the object type. 

Let’s see an example:

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


Although `arrays` are subtypes of the object type, the `array` object has a built-in  `toString()`  method that overrides the default  `Object.toString()`  method and returns a  `string`  representation containing each array element separated by a comma. This `toString()` method lives on the Array’s prototype  as `Array.prototype.toString()`.

Here is an example:

```js
// Calling toString() explicitly on an array
let array = []
array.toString() // ""  It returns an empty string.

let array1 = [1, 2, 3]
array1.toString() // "1,2,3"  It returns a string containing each element in the array seperated by a comma.
```

In a situation where you want to get the object class, you need to skip the default override behavior of `Array.prototype.toString` in favor of `Object.prototype.toString()` . 

You have to pass the  `array`  in the  `call()` method to change the context from `Array` to `Object`.

```js
console.log(Object.prototype.toString.call([1, 2, 3])) // [object Array] 
```    


## ToNumber()

Whenever we perform a numeric operation, and one or both operands aren't numbers, the  `ToNumber()`  abstract operation will be invoked to convert it to a value of type  `number` . 
Let's see some examples:

| Argument  | Result                                                                                                                                                      |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| undefined | NaN                                                                                                                                                         |
| null      | 0                                                                                                                                                           |
| true      | 1                                                                                                                                                           |
| false     | 0                                                                                                                                                           |
| “”        | 0                                                                                                                                                           |
| “.”       | NaN                                                                                                                                                         |
| “1”       | 1                                                                                                                                                           |
| BigInt    | Throw a type error exception.                                                                                                                               |
| symbol    | Throw a type error exception.                                                                                                                               |
| object    | 1.  [ToPrimitive](https://tc39.es/ecma262/#sec-toprimitive)(argument, number).<br>2. Return ? [ToNumber](https://tc39.es/ecma262/#sec-tonumber)(primValue). |

As seen from the table above, when  `ToNumber()`  is called on a non-primitive (any of the object types) value, it is first converted to its primitive equivalent by invoking  `ToPrimitive()` abstract operation and passing  `number`  as the PreferredType hint.
The return value from the  `ToPrimitive()`  operation will then be coerced into a  `number`  by the  `ToNumber()` abstract operation. If it still doesn't result in a primitive value, it throws an error.

Let's take an array with an empty string as an example.  

```js
ToPrimitive( [""], number) // It first calls the ToPrimitive abstract operation on it and pass number as the hint.

[""].valueOf() // [""]  Because  the hint is number, it calls valueOf first and this basically returns itself. So we still have the array with an empty string which isn’t a primitive.

[""].toString() // ""   It then calls the toString() function next and this will end up producing an empty string "".

ToNumber("") // 0   Next it calls ToNumber() and passes the empty string "" as an argument. As seen from the table above, that would result to 0.
```   


## ToBoolean()

The abstract operation   `ToBoolean()`  is called to convert an argument to a Boolean type whenever we use a value that is not  `Boolean`  in a place that needs a   `Boolean` . The  `ToBoolean()`  abstract operation does not invoke the  `ToPrimitive()`  or any of the other abstract operations.
It just checks to see if the value is either falsy or not. There is a lookup table in the spec that defines a list of values that will return  `false`  when coerced to a  `boolean` . They are called falsy values.  

These are the falsy values:

| Argument Type | Result |
| ------------- | ------ |
| undefined     | false  |
| null          | false  |
| false         | false  |
| NaN           | false  |
| 0             | false  |
| -0            | false  |
| “”            | false  |

Values that are not on the list of falsy values are truthy values.

## Conclusion 

Languages that take the first position about their rules are referred to as "strongly typed" because they are strict about not allowing you to break the rules.
Since JavaScript is not one of them, it is referred to as weakly or loosely typed because it gives room for a lot of flexibility in terms of implicit coercion, and you do not have to specify the type of your variables explicitly.

Like any other language, Javascript has its rules, and the rules that govern the type system of a language exist to help us. It is up to us to learn them to avoid unnecessary mistakes.

Thank you! I hope you enjoyed reading as much as I enjoyed writing ❤️.