DEV Community

Cover image for JavaScript Data Types
Julián Scialabba
Julián Scialabba

Posted on

JavaScript Data Types

In this post, I will explain each Javascript data type. I will focus on a simple definition of each data type and some characteristics and problems about them.

Primitive Data Types

A primitive data type is an immutable data that doesn't have either properties or methods.

There are 7 primitive types:

undefined, null, string, number, boolean, symbol (ES5), bigint (ES11)

Before I explain each type, I would like to introduce the typeof operator.

Typeof operator

To know the type of any value, you can use the typeof operator. The typeof operator returns a string which indicates the type of the parameter value.

typeof("Hola") // string
typeof(12) // integer
typeof(true) // boolean
typeof(undefined) // undefined
typeof(null) // object
typeof({}) // object
typeof(Symbol()) // symbol
typeof(1n) // bigint
typeof(function(){}) // function

In the previous example, there are two cases in which typeof operator doesn't return the expected data type:

  • typeof(null) = 'object'

  • typeof(function(){}) = 'function'

Later, I will explain the reason of this behavior.

Explaining each Primitive Data Type

Undefined

It is a data type which represents a non-defined value. It means that you declare a variable but you don't set a value. The variable will contain an undefined value.

The typeof operator with an undefined value or with a non-declared variable returns 'undefined'.

var a;
console.log(a); // undefined
console.log(typeof(a)) // 'undefined'
console.log(typeof(b)) // 'undefined'
console.log(b) // Uncaught ReferenceError: b is not defined

If you want to clean a variable, you can set undefined, but it's not a good practice. For this purpose you should use the data type null.

Null

It is a data type which represents the absence of value. It means that a variable contains nothing or you don't know its value yet.

You should set null instead of undefined when you want to indicate that the variable is defined but without a value.

const a = null;
console.log(a); // null
console.log(typeof(a)); // 'object'

Something interesting with null is that typeof(null) = 'object'

This is an unexpected behavior because null is a primitive data type, so typeof(null) should be null. It's a bug from the beginning of the language. There was a proposal to fix this bug but it was rejected because this fix would break a lot of existing deployed code. Here the link of the proposal: https://web.archive.org/web/20160331031419/http://wiki.ecmascript.org:80/doku.php?id=harmony:typeof_null

String

It's a data type which represents text. It is used to store and manipulate text.

You can define a string using single quotation marks, double quotation marks or backticks. With backticks, you can concatenate and interpolate variables inside the string.

const name = 'Julian'; // Single quotation marks
const lastname = "Scialabba"; // Double quotation marks
const hello = `Hello ${name} ${lastname}`; // Backsticks -> Hello Julian Scialabba
console.log(typeof(hello)); // 'string'

String is a primitive data type. It means that strings don't have methods. But, why can we do something like that?:

const name = 'Julian';
name.length // 6 
name.toUpperCase(); // JULIAN  
//Why this string primitive has attributes and methods?

You can see that our string primitive is behaving like an object because it has properties and methods. And that is what really happens because it is an object. JavaScript internally creates a wrapper object that provides functionalities to our string primitive. JavaScript has wrapper objects for all primitives data types except for null and undefined. For this reason, null and undefined don't have methods and we usually find errors like:

var a;
var b = null;
var c = 'Hello';
console.log(a.toString()); // TypeError: a is undefined
console.log(b.toString()); // TypeError: b is null
console.log(c.toString()); // Hello

You can also create a wrapper object on your own:

const name = String('Julian');
console.log(typeof(name)); // 'string'

This way of creating a String is useful because it allows us to cast values. For example:

const numberStr = String(123);
const boolStr = String(true);
console.log(numberStr) // '123'
console.log(boolStr) // 'true'

You can also create a wrapper object with 'new' operator:

const name = new String('Julian');
console.log(typeof(name)); // 'object'

But, this way of creating a primitive value is not recommended. It is inefficient and it could cause confusion because the type of the variable is 'object' instead of 'string'.

You can create strings through Javascript type coercion, too. For example:

const age = 29;
const ageFromWrapperInteger = age.toString() // '29'
const ageCoercionStr = 29 + ''; // '29'
const isMaleCoercionStr = true + ''; // 'true'
console.log(typeof(ageFromWrapperInteger)); // 'string'
console.log(typeof(ageCoercionStr)); // 'string'
console.log(typeof(isMaleCoercionStr)); // 'string'

Number

It's a data type which represents numbers with or without decimals. It is used to store and operat numbers.

You can define a number using numbers without quotes or with the number wrapper object.

const number = 29;
const numberWithDecimals = 3.14;
const numberObjectWrapper = Number('2'); // 2
typeof(number); // 'number'
typeof(numberWithDecimals); // number
typeof(numberObjectWrapper); // number

Numbers in Javascript are encoded as double precision floating point numbers, following the international IEEE 754 standard. This format uses 64 bits to store the numbers. As a result of this representation, we have to consider two situations when we work with numbers:

  1. Safe integers
  2. Operations between decimal numbers

Safe Integers

Numbers in JavaScript have a safe range in which you can operate accurately:

[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] = [-2**53+1, 2**53-1] = [-9007199254740991, 9007199254740991]

If you operate outside the limits, your operations won't have precision and some numbers might not be represented. For example:

const maxSafe = Number.MAX_SAFE_INTEGER; //9007199254740991
const x = maxSafe + 1;  //9007199254740992
const y = maxSafe + 2;  //9007199254740992
x === y // true

ES11 specification added a new primitive data type to handle numbers larger than 2**53 - 1. It is called BigInt and I will explain it later.

Operations between decimal numbers

Numbers in JavaScript are stored in base-2 (binary) system. There are some decimal fractions that can't be represented exactly as binary fractions. For that reason, we have problems like the following:

0.2 + 0.1 // 0.30000000000000004
0.2 - 0.3 // -0.09999999999999998

You should be careful when you operate with fractions and when you need precision on your operations. To handle this problem, we have some options:

Work with a library:

If you need precision, you can work with a library, like Big Decimal, that handles this problem.

Round numbers:

You can round numbers with the method Number.toFixed(digits)

const number = 0.1 + 0.2; //// 0.30000000000000004
const numberFixedStr = number.toFixed(2); // '0.30'
const numberFixed = +number.toFixed(2); // 0.3

You can see that toFixed returns a string. If you want to get an integer number, you have to write a + at the beginning of the result (it coerces from String to Number types).

Work with integers:

Another option is to transform all our decimal numbers to integer numbers. Then you operate with integers, and finally you return the decimal number. For example:

const number1 = 0.1 * 100;
const number2 = 0.2 * 100;
(number1 + number2) / 100; // 0.3

NaN: Not a Number

The NaN value is a number that represents invalid numbers. There is a method isNaN(value) that indicates if a value is a NaN.

const x = 0/0; // NaN
const y = 1 * undefined; // NaN
const z = Number('Sarasa'); // NaN
typeof(x); // 'number'
typeof(y); // 'number'
typeof(z); // 'number'
x === y; // false
NaN === NaN // false
isNaN(x); // true

The value NaN isn't equal to any value, not even to NaN.

Boolean

It's a data type which represents the two boolean values: true or false.

const isValid = true;
const isFetching = false;
const isLoading = Boolean(true);
typeof(isValid); // 'boolean'
typeof(isFetching); // 'boolean'
typeof(isLoading); // 'boolean'

You can create boolean values as in the previous example or you can create boolean values with:

  • Comparison and logical operators
  • Truthy and falsy values.

Comparison and logical operators

The comparison and logical operators are used to test any value data type and return true or false.

Comparison Operators:
  • Equal value: ==
  • Equal value and type: ===
  • Not equal value: !=
  • Not equal value and not equal type: !==
  • Greather than: >
  • Less than: <
  • Greather than or equal to: >=
  • Less than or equal to: <=

Examples:

'123' == 123; // true
'123' === 123; // false
'123' != 123; // false
'123' !== 123; // true
123 > 123; // false
123 >= 123 // true
123 < 100 // false
123 <= 122 // true
Logical Operators
  • Not: !
  • And: &&
  • Or: ||

Examples:

!true // false
true && false // false
true || false // true
true && !false // true

Comparison and Logical Operators can be used in conditional statements:

const isValid = true;
const isFetching = false;
const name = '';
if(isValid && !isFetching){
 console.log('Fetch values');
}
if(!name) {
 console.log('User without name');
}

In the first if you can see that the condition is based on two boolean values. However, in the second if the condition is a String value. Why can we use any data type inside the conditional statement? It is due to Javascript type coercion and truthy and falsy values.

Falsy and truthy values

Javascript type coercion is the implicit conversion of values from one data type to another. This conversion is done automatically by the Javascript engine. Here, some examples:

'1' + 1 // '11' -> Converts Number to String and concat strings
'1' - 1 // 0 -> Converts String to Number and substract numbers
'1' * 1 // 1 -> Converts String to Number and multiply numbers
'1' + true // '1true' -> Converts Boolean to String and concat strings
1 + true // 2 -> Converts Boolean to Number and sum numbers. True = 1
1 + false // 1 -> Converts Boolean to Number and sum numbers. False = 0
'1' - true // 0 -> Converts String and Boolean to Numbers and substract numbers 

If a value is inside of a conditional statement, it will be coerced as boolean type. The values that are coerced as true are called 'Truthy Values' and the values that are coerced as false are called 'Falsy Values'.
All the values that are not falsy are truthy values. For this reason, we only have to know the falsy values:

  • Number zero: 0
  • BigInt zero: 0n
  • Empty string: ''
  • Null: null
  • Undefined: undefined
  • Not a Number: NaN
Boolean(0) // false
Boolean(0n) // false
Boolean('') // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean({}) // true -> Empty object is truthy
Boolean([]) // true -> Empty array is truthy
Boolean('Hello') // true

Symbol

It's a data type that was introduced in ES5 and represents an unique identifier. It can be created using Symbol() with an optional string as parameter.

const aSymbol = Symbol();
const symbolId = Symbol('id');
const anotherSymbolId = Symbpl('id');
typeof(aSymbol) // 'symbol'
symbolId === anotherSymbolId // false

Each symbol has to be unique. For this reason, the equal comparison between two symbols with the same description is false. Every time you create a Symbol(), you will get a new and unique symbol that will be different from others.

Symbol() === Symbol() // false

Symbols are used to:

  1. Identify unique and hidden properties on objects.
  2. Identify a list of unique constant values.
  3. Implement Well-Known Symbols to change some characteristics of the language: https://tc39.es/ecma262/#table-1

BigInt

It's a data type that was introduced in ES11. It represents numbers larger than 2**53 - 1, which is the limit for the primitive data type number. BigInt provides support for integers of arbitrary length but not for decimal values.

A BigInt can be created adding an n at the end of the number or with the wrapper object BigInt(value)

const bigIntValue = BigInt(1); // 1n
const anotherBigIntValue = BigInt('9999') // 9999n
bigIntValue + anotherBigIntValue // 10000n
const maxSafeInteger = 9007199254740991n; // MAX_SAFE_INTEGER
const maxSafeInteger1 = maxSafeInteger + 1n; // 9007199254740992n
const maxSafeInteger2 = maxSafeInteger + 2n; // 9007199254740993n -> With numbers we can't get this value
maxSafeInteger1 !== maxSafeInteger2 // true
typeof(maxSafeInteger) // 'bigint'

The available operators for BigInt are: +, *, -, **, /, %. You can't operate between a BigInt and a Number with those operators. You can't either operate with the Math library because this library uses data type Number.

const maxSafeInteger = 9007199254740991n;
maxSafeInteger + 9n // 9007199254741000n
maxSafeInteger - 1n // 9007199254740990n
maxSafeInteger * 2n // 18014398509481982n
maxSafeInteger ** 2n // 81129638414606663681390495662081n
5n / 2n // 2n -> It is rounded towards 0 because BigInt doesn't represent decimals
maxSafeInteger % 2n // 1n
maxSafeInteger + 2 // can't convert BigInt to number
Math.pow(3n,2) // can't convert BigInt to number

Comparisons between BigInt and Number work fine:

3n > 2 // true
1n > 2 // false
2n >= 2 // true
2n == 2 // true
2n === 2 // false -> The types are different

And finally, the only falsy value of BigInt is 0n.

Boolean(0n) // false
Boolean(1n) // true

It's not recommended that you coerce BigInt to Number. If you need numbers lower than 2**53 -1, you should use Number data type. If you need numbers higher than 2**53 -1, you should use BigInt data type.

If you coerce a BigInt higher than 2**53 -1 to number, you might loss precision.

Number(1n) //1
Number(9007199254740993n) //9007199254740992

Object Data Type

In JavaScript, every value that is not a primitive data type is an object data type.

Object data type is used to represent values with properties and methods. While primitive data types represent a simple immutable value, an object data type represent collection of data and more complex entities.

The typical Javascript key-value structure is an object data type.

const person = {
  name: 'Julian',
  lastname: 'Scialabba'
};
typeof(person); // 'object'

Other structures like Array, Function, Set, HashMap, Date, among others, are objects data type, too.

const numbers = [1, 2, 3, 4];
const aSet = new Set();
const aDate = new Date();
const sayHello = function(name){
  console.log(`Hello ${name}!`);
}
typeof(numbers) // 'object'
typeof(aSet) // 'object'
typeof(aDate) // 'object'
typeof(sayHello) // 'function'

We might expect that typeof(function(){}) = 'object' but instead is typeof(function(){}) = 'function' . It can be explained because ECMAScript specification defines typeof operator as follows:

Typeof Val Result
Undefined 'undefined'
Null 'object'
Boolean 'boolean'
Number 'number'
String 'string'
Object (Not implement [[Call]]) 'object'
Object (Implement [[Call]]) 'function'

So, typeof(function(){}) = 'function' because function is an object that implements [[Call]].

Conclusion

I have explained every primitive data type and the object data type. I have focused on a simple definition of each data type and some characteristics and problems about them.

I hope that you like this post! Let me know your thoughts on the comments.

Thanks for reading!

Top comments (4)

Collapse
 
ephraimdavid22 profile image
Agbeze_Obinna • Edited

If a string is an example of primitive type which doesn't have a prop or method, then why this...??

//string methods

'obinna ephraim'.toLowerCase() 
'Nora dimma'.trim()
Collapse
 
juliansci profile image
Julián Scialabba

Hi!
I have explained it in the String section. It is because Javascript internally creates an object wrapper for each primitive type (except null and undefined).
For this reason the primitive values are treated as objects when executing methods and properties.
In your example, Javascript internally does this:

String('obinna ephraim').toLowerCase();
String('Nora dimma').trim();
Collapse
 
pentacular profile image
pentacular

I think it's important to say that it 'effectively' does this.

It's unlikely that an implementation will actually do that, and probable that there will be some efficient dispatch table somewhere that does the right thing without going around creating lots of ephemeral String objects. :)

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt • Edited

Not sure whether it is preferred, but typeof doesn't really need brackets. Also, it is classified as an operator.

Another curious thing is object. You know, that Object.create(null) vs Object.create({}).

Also, typeof null === 'object'.