This was originally published on The Brewing Press
For those who just wants the answers, feel free to jump to the 'Summary' at the end.
There are a lot of confusion out there as to whether JavaScript is an Object-Orientated programming (OOP) language, or a functional language. Indeed, JavaScript can work as either.
But this lead people to ask "Are everything in JavaScript objects?", "What about functions?"
This post will clear all this up.
Let's start at the start
In JavaScript, there are six primitive data types:
- Booleans -
true
orfalse
null
undefined
-
number
- double-precision 64-bit float. There are no integers in JavaScript. string
-
symbol
(new in ES6)
In addition to these six primitive types, the ECMAScript standard also defines an Object
type, which is simply a key-value store.
const object = {
key: "value"
}
So, in short, anything that is not a primitive type, is an Object
, and this includes functions and arrays.
All functions are objects.
// Primitive types
true instanceof Object; // false
null instanceof Object; // false
undefined instanceof Object; // false
0 instanceof Object; // false
'bar' instanceof Object; // false
// Non-primitive types
const foo = function () {}
foo instanceof Object; // true
Primitive types
Primitive types have no methods attached to them; so you'll never see undefined.toString()
. Also because of this, primitive types are immutable, because they have no methods attached that can mutate it.
You can reassign a primitive type to a variable, but it will be a new value, the old one is not, and cannot, be mutated.
const answer = 42
answer.foo = "bar";
answer.foo; // undefined
Primitive types are immutable
Furthermore, the primitive types are stored as the value themselves, unlike objects, which are stored as a reference. This has implications when performing equality checks.
"dog" === "dog"; // true
14 === 14; // true
{} === {}; // false
[] === []; // false
(function () {}) === (function () {}); // false
Primitive types are stored by value, objects are stored by reference
Functions
A function is a special type of object, with some special properties, such as constructor
and call
.
const foo = function (baz) {};
foo.name; // "foo"
foo.length; // 1
And just like a normal objects, you can add new properties to the object:
foo.bar = "baz";
foo.bar; // "baz"
This makes functions a first-class citizen, because it can be passed around, as arguments into other functions, just like any other objects could.
Methods
A method is a object property that also happens to be a function.
const foo = {};
foo.bar = function () { console.log("baz"); };
foo.bar(); // "baz"
Constructor Functions
If you have several objects which share the same implementation, you can place that logic inside a constructor function, and then user the constructor function to create those objects.
A constructor function is no different from any other function. A function is used as a constructor function when it is used after the new
keyword.
Any function can be a constructor function.
const Foo = function () {};
const bar = new Foo();
bar; // {}
bar instanceof Foo; // true
bar instanceof Object; // true
A constructor function will return an object. You can use this
inside the function body to assign new properties to the object. So if we want to make many objects with the property bar
initialized to the value "baz"
, then we can create a new constructor function Foo
that encapsulates that logic.
const Foo = function () {
this.bar = "baz";
};
const qux = new Foo();
qux; // { bar: "baz" }
qux instanceof Foo; // true
qux instanceof Object; // true
You can use constructor functions to create a new object.
Running a constructor function, like Foo()
, without new will run Foo
like a normal function. this
inside the function would correspond to the execution context. So if we call Foo()
outside of all functions, it will actually modify the window
object.
Foo(); // undefined
window.bar; // "baz"
Conversely, running a normal function as a constructor function would normally return a new empty object, as you have already seen.
const pet = new String("dog");
Wrapper Objects
The confusion arises because of functions like String
, Number
, Boolean
, Function
etc. which, when called with new
, creates wrapper objects for these primitive types.
String
is a global function that creates a primitive string when passed in an argument; it will try to convert that argument into a string.
String(1337); // "1337"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
String(); // ""
String("dog") === "dog" // true
typeof String("dog"); // "string"
But you can also use the String
function as a constructor function.
const pet = new String("dog")
typeof pet; // "object"
pet === "dog"; // false
And this will create a new object (often referred to as wrapper object) representing the string "dog"
, with the following properties:
{
0: "d",
1: "o",
2: "g",
length: 3
}
Object wrappers are often referred to as wrapper objects, too. Go figure.
Auto-Boxing
What's interesting is that the constructor of both the primitive strings and the object are both the String
function. What's even more interesting is the fact that you can call .constructor
on the primitive string, when we've already covered that primitive types cannot have methods!
const pet = new String("dog")
pet.constructor === String; // true
String("dog").constructor === String; // true
What's happening is a process called autoboxing. When you try to call a property or method on certain primitive types, JavaScript will first convert it into a temporary wrapper object, and access the property / method on it, without affecting the original.
const foo = "bar";
foo.length; // 3
foo === "bar"; // true
In the above example, to access the property length
, JavaScript autoboxed foo
into a wrapper object, access the wrapper object's length
property, and discards it afterwards. This is done without affecting foo
(foo
is still a primitive string).
This also explains why JavaScript doesn't complain when you try to assign a property to a primitive type, because the assignment is done on that temporary wrapper object, not the primitive type itself.
const foo = 42;
foo.bar = "baz"; // Assignment done on temporary wrapper object
foo.bar; // undefined
It will complain if you try this with a primitive type which does not have a wrapper object, such as undefined
or null
.
const foo = null;
foo.bar = "baz"; // Uncaught TypeError: Cannot set property 'bar' of null
Summary
- Not everything in JavaScript is an object
- There are 6 primitive types in JavaScript
- Everything that's not a primitive type is an object
- Functions are just a special type of object
- Functions can be used to create new objects
- Strings, booleans and numbers can be represented as a primitive type but also as an object
- Certain primitive types (strings, numbers, booleans) appear to behave like objects because of a JavaScript featured called autoboxing.
Top comments (10)
Well, actually:
All of that is valid JS (including the comments between
0
and that period).Great article.
Remember kiddos,
String("string") == "string";
but
String("string") !== "string";
Hrmmm..... I had done basically the same thing in my console when I made my comment, but I had a different result that day. I honestly expected your to be the case but I got what I typed before. I must have had a typo or something. MB
I think you forgot the new keyword:
That's most likely it. You're far more knowledgeable than me in this area it seems. Thanks for adding the clarity for future readers and teaching me something!
I am definitely bookmarking this! Thanks
Super practical article, thanks a lot Daniel.
Awesome, now I have a cheatsheet. π Super helpful for when I practice some JS. Thanks!
Excelent article !! :)