- There simple primitives (
undefined) which are not objects
- However, everything else is indeed an object: simple objects, arrays, functions, ...
- Look like actual types, even classes
- In reality, they are actually just built-in functions
String (note how it behaves differently when it's called as constructor):
Although it is preferred to represent strings as normal
string values, all of the interesting methods are defined on
String as needed.
When not called as constructor,
String (and also
Boolean) is useful for performing type conversions:
The prototype chain
[[Prototype]] link of the object. If it's not found on that object, it follows that object's
[[Prototype]] link, until the end of the chain is reached. The chain formed by the
[[Prototype]] links is called the prototype chain.
The prototype chain typically ends at the built-in
__proto__ property that exposes the object's internal
[[Prototype]] property, but its use is deprecated. It's better to use
In the above example, we can say that
objectB prototypically inherits from
Note: an object can only prototypically inherit from a single other object. There is no multiple inheritance here.
Shadowing occurs when a property on an object hides a property with the same name deeper down the prototype chain. This can often be a source of confusion. Therefore, it's recommended to avoid creating properties with a name that already exists somewhere in the chain.
It's easy to accidentally shadow a property! Example:
Here, the line
objectB.counter++ is equivalent to
objectB.counter = objectB.counter + 1, which:
- Looks for a property
counteron the prototype chain and finds it on
- Gets the value of that property
- Creates a new property
From that point on,
objectB has its own
counter property shadowing the
counter property on
Shadowing is not only an issue with simple properties but also with methods (which are just properties with a function as value). Therefore, it is recommended to avoid creating different methods with the same name in a prototype chain.
Property shadowing can also be caused by unexpected input from the outside and it can hide methods that are defined on the
Object prototype itself. Example:
You can mitigate the risk that this happens by checking user input and rejecting input that contains unexpected properties. It also helps to avoid calling methods provided through
Object.prototype on the object itself (first approach in the above code) but instead explicitly use the function from
Object.prototype (second approach in the code). There is actually a rule in ESLint (no-prototype-builtins ) that enforces this.
Prototype pollution is a vulnerability in code that allows input coming from the outside to mess up your prototype chain.
Prototype pollution vulnerabilities could be exploited to prevent system from working normally (DOS attack), set certain properties that bypass normal access checks or even inject some code into an application (see Prototype pollution attacks in NodeJS applications ).
Some ways to avoid being vulnerable:
- If you need to write your own recursive merge, make sure to have it ignore
- Vulnerabilities in libraries like Lodash have already been patched
- Validate all user input, rejecting any input with unexpected properties
- An interesting way to avoid this kind of attack is to freeze the Object prototype. If you add
Object.freeze(Object.prototype);on top of the example above, the vulnerability is gone. This works because the freeze prevents the prototype from being altered in any way.
prototype property. You could call this the function's prototype, although some would say that's confusing.
In any case, every object that is constructed through the function using
new syntax will have a
[[Prototype]] linking to the function's
As the above code shows, we can add behavior to the function's
prototype property by defining properties on it with a function as value. We can call these functions through the new object (looking up
log works the same as looking up any other property on the prototype chain).
You can also see that lookups in the prototype chain happen in a dynamic way (
[[Prototype]] links point to objects, no "copying of behavior" is done, there is just a chain which is traversed whenever properties are looked up). We can add a method to
Test's prototype, even after we have used it to create
a can use that method. We can later even overwrite the implementation with a new one. This offers a lot of possibilities, but could also be confusing.
Prototypical inheritance and object-oriented design
The example regarding functions and
prototype already showed something that looked like classes. We can use the same mechanisms to simulate something like class inheritance:
If you just look at the lines at the bottom, the syntax seems very similar to for example Java. However, it is still just prototypical inheritance at work, with property lookups following the prototype chain at runtime.
Object-oriented design vs. delegation-oriented design
Example which does the same as the object-oriented example above:
Note that the "constructor" functionality, which sat in the functions
ClassB in the previous example, is now implemented as separate
initB functions on the objects, as there is no other place to put it.
Comparison of how the two approaches compare in terms of objects created and their relationships (adapted from You Don't Know JS ):
- New syntax introduced in ES6
- In the background we still just have prototypes and the prototype chain!
Example of how to implement the example from above using ES6 classes:
Leads to pretty clean syntax, although it is still important to remember that JS didn't suddenly get a new class inheritance mechanism!
Example indicating that we're still using runtime delegation based on prototypes:
Example indicating that accidental shadowing is still possible:
The code below is an example of using public instance fields:
For some info on private instance fields, see below: Encapsulation using private fields and methods
Static (class-level) fields
You can use static fields if you want a field to exist at the level of the class rather than at the level of its instances. This feature was added long after classes were introduced (check browser support on Can I Use ).
For some info on private static fields, see below: Encapsulation using private fields and methods
Simulating static class fields
If you need to keep a shared property at the level of the class but you can't use static fields, the best approach is probably to set it directly on the class (rather than, for example, on the
.prototype of the class where the methods sit). Note: setting the property directly on the class is also how TypeScript implements static class properties.
Working example putting properties directly on the class:
Accidental shadowing when putting on the
One important concept in object-oriented design is encapsulation: classes expose a public interface while hiding internals using private fields and private methods.
Example of a class without any encapsulation:
Encapsulation using private fields and methods
This feature was added long after classes were introduced (check browser support on Can I Use )
Example using private fields:
Note that private fields cannot be added dynamically, they can only be declared up-front
Private fields can also be static (class-level). In this case, it's recommended to always access them through the class itself rather than using
this in the code for a static method, since the latter can lead to some confusing errors.
In addition to private fields, you can also add private (static) methods
Encapsulation without using private fields and methods
Example simulating private fields using closures (drawback: methods are created again for every instance instead of just putting them on the prototype):
Simplest workaround: convention that properties starting with underscore (
_) are private. Example: